Procházet zdrojové kódy

人才库改为精英储备(文件移动)

lifanagju_citu před 8 měsíci
rodič
revize
a085d93596
20 změnil soubory, kde provedl 1719 přidání a 36 odebrání
  1. 0 0
      src/api/recruit/enterprise/talentPool/index.js
  2. 1 1
      src/layout/enterprise.vue
  3. 60 29
      src/router/modules/components/recruit/enterprise.js
  4. 1 1
      src/views/recruit/enterprise/personnelManagement/components/table.vue
  5. 2 2
      src/views/recruit/enterprise/resumeManagement/talentPool/index copy.vue
  6. 2 2
      src/views/recruit/enterprise/resumeManagement/talentPool/index.vue
  7. 107 0
      src/views/recruit/enterprise/talentPool/components/details.vue
  8. 77 0
      src/views/recruit/enterprise/talentPool/components/details/attachmentResume.vue
  9. 123 0
      src/views/recruit/enterprise/talentPool/components/details/baseInfo.vue
  10. 75 0
      src/views/recruit/enterprise/talentPool/components/details/dict.js
  11. 64 0
      src/views/recruit/enterprise/talentPool/components/details/educationExp.vue
  12. 58 0
      src/views/recruit/enterprise/talentPool/components/details/jobIntention.vue
  13. 45 0
      src/views/recruit/enterprise/talentPool/components/details/projectExperience.vue
  14. 44 0
      src/views/recruit/enterprise/talentPool/components/details/trainingExperience.vue
  15. 51 0
      src/views/recruit/enterprise/talentPool/components/details/vocationalSkills.vue
  16. 51 0
      src/views/recruit/enterprise/talentPool/components/details/workExperience.vue
  17. 383 0
      src/views/recruit/enterprise/talentPool/components/filter.vue
  18. 355 0
      src/views/recruit/enterprise/talentPool/index copy.vue
  19. 219 0
      src/views/recruit/enterprise/talentPool/index.vue
  20. 1 1
      src/views/recruit/enterprise/talentRecruitment/components/table.vue

+ 0 - 0
src/api/recruit/enterprise/resumeManagement/talentPool/index.js → src/api/recruit/enterprise/talentPool/index.js


+ 1 - 1
src/layout/enterprise.vue

@@ -43,7 +43,7 @@ const key = computed(() => {
 })
 
 const whiteList = [
-  '/recruit/enterprise/resumeManagement/talentPool/details/details',
+  '/recruit/enterprise/talentPool/details/details',
   '/recruit/enterprise/purchasePackage',
   '/recruit/enterprise/systemManagement/groupAccount/invite/0',
   '/recruit/enterprise/systemManagement/groupAccount/invite/1'

+ 60 - 29
src/router/modules/components/recruit/enterprise.js

@@ -37,46 +37,29 @@ const enterprise = [
       }
     ]
   },
-  // {
-  //   path: '/recruit/enterprise/eliteReserve',
-  //   component: Layout,
-  //   name: 'Talent Recruitment',
-  //   meta: {
-  //     title: '精英储备',
-  //     enName: 'Elite Reserve',
-  //     icon: 'mdi-account-settings-outline'
-  //   },
-  //   children: [
-  //     {
-  //       path: '/recruit/enterprise/eliteReserve',
-  //       show: true,
-  //       component: () => import('@/views/recruit/enterprise/eliteReserve/index.vue')
-  //     }
-  //   ]
-  // },
   {
-    path: '/recruit/enterprise/resumeManagement',
-    redirect: '/recruit/enterprise/resumeManagement/talentPool',
+    path: '/recruit/enterprise/talentPool',
     component: Layout,
-    name: 'Resume Management',
+    name: 'Elite Reserve',
     meta: {
-      title: '简历管理',
-      enName: 'Resume Management',
-      icon: 'mdi-account-settings-outline'
+      title: '精英储备',
+      enName: 'Job Management',
+      icon: 'mdi-format-list-bulleted-square'
     },
     children: [
       {
-        path: '/recruit/enterprise/resumeManagement/talentPool',
+        path: '/recruit/enterprise/talentPool',
+        show: true,
         meta: {
-          title: '人才库',
-          enName: 'Talent Pool'
+          title: '职位列表',
+          enName: 'Job list'
         },
-        component: () => import('@/views/recruit/enterprise/resumeManagement/talentPool/index.vue')
+        component: () => import('@/views/recruit/enterprise/talentPool/index.vue')
       },
       {
-        path: '/recruit/enterprise/resumeManagement/talentPool/details/:id',
+        path: '/recruit/enterprise/talentPool/details/:id',
         show: true, // 侧边栏不展示
-        component: () => import('@/views/recruit/enterprise/resumeManagement/talentPool/components/details'),
+        component: () => import('@/views/recruit/enterprise/talentPool/components/details'),
         name: 'talentPoolDetails',
         meta: {
           title: '人才详情',
@@ -85,6 +68,54 @@ const enterprise = [
       },
     ]
   },
+  // {
+  //   path: '/recruit/enterprise/eliteReserve',
+  //   component: Layout,
+  //   name: 'Talent Recruitment',
+  //   meta: {
+  //     title: '精英储备',
+  //     enName: 'Elite Reserve',
+  //     icon: 'mdi-account-settings-outline'
+  //   },
+  //   children: [
+  //     {
+  //       path: '/recruit/enterprise/eliteReserve',
+  //       show: true,
+  //       component: () => import('@/views/recruit/enterprise/eliteReserve/index.vue')
+  //     }
+  //   ]
+  // },
+  // {
+  //   path: '/recruit/enterprise/resumeManagement',
+  //   redirect: '/recruit/enterprise/talentPool',
+  //   component: Layout,
+  //   name: 'Resume Management',
+  //   meta: {
+  //     title: '简历管理',
+  //     enName: 'Resume Management',
+  //     icon: 'mdi-account-settings-outline'
+  //   },
+  //   children: [
+  //     {
+  //       path: '/recruit/enterprise/talentPool',
+  //       meta: {
+  //         title: '人才库',
+  //         enName: 'Talent Pool'
+  //       },
+  //       component: () => import('@/views/recruit/enterprise/talentPool/index.vue')
+  //     },
+  //     {
+  //       path: '/recruit/enterprise/talentPool/details/:id',
+  //       show: true, // 侧边栏不展示
+  //       component: () => import('@/views/recruit/enterprise/talentPool/components/details'),
+  //       name: 'talentPoolDetails',
+  //       meta: {
+  //         title: '人才详情',
+  //         hideSide: true
+  //       }
+  //     },
+  //   ]
+  // },
   {
     path: '/recruit/enterprise/position',
     component: Layout,

+ 1 - 1
src/views/recruit/enterprise/personnelManagement/components/table.vue

@@ -99,7 +99,7 @@ watch(
 // 人才详情
 const handleToPersonDetail = ({ userId, id }) => {
   if (!userId || !id) return
-  window.open(`/recruit/enterprise/resumeManagement/talentPool/details/${userId}?id=${id}`)
+  window.open(`/recruit/enterprise/talentPool/details/${userId}?id=${id}`)
 }
 
 // 入职

+ 2 - 2
src/views/recruit/enterprise/resumeManagement/talentPool/index copy.vue

@@ -105,7 +105,7 @@ import Snackbar from '@/plugins/snackbar'
 import TextUI from '@/components/FormUI/TextInput'
 import FilterPage from './components/filter.vue'
 import { dealDictArrayData } from '@/utils/position'
-import { getTalentPoolPage } from '@/api/recruit/enterprise/resumeManagement/talentPool'
+import { getTalentPoolPage } from '@/api/recruit/enterprise/talentPool'
 import { removeFormTalentPool } from '@/api/recruit/enterprise/personnel'
 
 
@@ -282,7 +282,7 @@ const openDrawer = () => {
 // 人才详情
 const talentPoolDetails = ({ userId, id }) => {
   if (!userId || !id) return
-  window.open(`/recruit/enterprise/resumeManagement/talentPool/details/${userId}?id=${id}`)
+  window.open(`/recruit/enterprise/talentPool/details/${userId}?id=${id}`)
 }
 </script>
 

+ 2 - 2
src/views/recruit/enterprise/resumeManagement/talentPool/index.vue

@@ -64,7 +64,7 @@ import { computed, reactive, ref } from 'vue'
 import Snackbar from '@/plugins/snackbar'
 import FilterPage from './components/filter.vue'
 import { dealDictArrayData } from '@/utils/position'
-import { getTalentPoolPage } from '@/api/recruit/enterprise/resumeManagement/talentPool'
+import { getTalentPoolPage } from '@/api/recruit/enterprise/talentPool'
 import { removeFormTalentPool } from '@/api/recruit/enterprise/personnel'
 import { timesTampChange } from '@/utils/date'
 import { getUserAvatar } from '@/utils/avatar'
@@ -148,7 +148,7 @@ const badgeIcon = computed(() => (item) => {
 // 人才详情
 const talentPoolDetails = ({ userId, id }) => {
   if (!userId || !id) return
-  window.open(`/recruit/enterprise/resumeManagement/talentPool/details/${userId}?id=${id}`)
+  window.open(`/recruit/enterprise/talentPool/details/${userId}?id=${id}`)
 }
 </script>
 

+ 107 - 0
src/views/recruit/enterprise/talentPool/components/details.vue

@@ -0,0 +1,107 @@
+<!-- 人才库 - 人才详情 -->
+<template>
+  <div class="d-flex justify-center mb-8">
+    <div v-if="Object.keys(cvData).length" style="width: 940px;background: #fff;" class="px-8 pb-12 pt-3 my-n3 mr-3">
+      <!-- 基本信息 -->
+      <baseInfo class="mt-5" :data="cvData.person"></baseInfo>
+      <!-- 个人优势 -->
+      <div class="d-flex mt-8">
+        <span class="mr-6">{{ $t('resume.personalAdvantages') }}</span>
+        <div style="flex: 1; white-space: pre-line; font-size: 15px;" v-if="cvData?.person?.advantage" v-html="cvData.person.advantage"></div>
+      </div>
+      <!-- 职业技能 -->
+      <div class="d-flex mt-8">
+        <span class="mr-6">{{ $t('resume.vocationalSkills') }}</span>
+        <vocationalSkills style="flex: 1;" :data="cvData.skillList"></vocationalSkills>
+      </div>
+      <!-- 求职意向 -->
+      <div class="d-flex mt-8">
+        <span class="mr-6">{{ $t('resume.jobIntention') }}</span>
+        <jobIntention style="flex: 1;" :data="cvData.interestedList"></jobIntention>
+      </div>
+      <!-- 工作经历 -->
+      <div class="d-flex mt-8">
+        <span class="mr-6">{{ $t('resume.workExperience') }}</span>
+        <workExperience style="flex: 1;" :data="cvData.workList"></workExperience>
+      </div>
+      <!-- 项目经历 -->
+      <div class="d-flex mt-8">
+        <span class="mr-6">{{ $t('resume.projectExperience') }}</span>
+        <projectExperience style="flex: 1;" :data="cvData.projectList"></projectExperience>
+      </div>
+      <!-- 培训经历 -->
+      <div class="d-flex mt-8">
+        <span class="mr-6">{{ $t('resume.trainingExperience') }}</span>
+        <trainingExperience style="flex: 1;" :data="cvData.trainList"></trainingExperience>
+      </div>
+      <!-- 教育经历 -->
+      <div class="d-flex mt-8">
+        <span class="mr-6">{{ $t('resume.educationExp') }}</span>
+        <educationExp style="flex: 1;" :data="cvData.eduList"></educationExp>
+      </div>
+      <!-- 附件简历 -->
+      <!-- <div class="d-flex mt-8">
+        <span class="mr-6">{{ $t('resume.attachmentResume') }}</span>
+        <attachmentResume style="flex: 1;"></attachmentResume>
+      </div> -->
+    </div>
+    <!-- <div class="operate pa-3">
+      <v-list>
+        <v-list-subheader class="title">简历助手</v-list-subheader>
+        <v-list-item
+          v-for="(item, i) in operateItems" :key="'简历助手' + i"
+          color="primary"
+          :prepend-icon="item.icon"
+          :title="item.text"
+          @click="{}"
+        >
+        </v-list-item>
+      </v-list>
+    </div> -->
+  </div>
+</template>
+
+<script setup>
+defineOptions({name: 'enterprise-talentPool-details'})
+import baseInfo from './details/baseInfo.vue'
+import vocationalSkills from './details/vocationalSkills.vue'
+import jobIntention from './details/jobIntention.vue'
+import workExperience from './details/workExperience.vue'
+import projectExperience from './details/projectExperience.vue'
+import trainingExperience from './details/trainingExperience.vue'
+import educationExp from './details/educationExp.vue'
+// import attachmentResume from './details/attachmentResume.vue'
+import { getPersonCvDetail } from '@/api/enterprise'
+import { ref } from 'vue'
+import { useRouter, useRoute } from 'vue-router'
+
+const route = useRoute()
+const router = useRouter()
+// const operateItems = [
+//   { text: '邀请面试', icon: 'mdi-account-check' },
+//   { text: '不合适', icon: 'mdi-close-circle-outline' },
+//   { text: '发起对话', icon: 'mdi-chat-processing-outline' },
+//   { text: '加入人才库', icon: 'mdi-tab-plus' },
+//   { text: '操作记录', icon: 'mdi-clock-edit-outline' },
+// ]
+
+// 获取人才详情
+const cvData = ref({})
+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
+}
+getCvDetail()
+
+</script>
+<style lang="scss" scoped>
+.operate {
+  width: 240px;
+  height: 500px; // 272px
+  position: sticky;
+  top: 60px;
+}
+</style>

+ 77 - 0
src/views/recruit/enterprise/talentPool/components/details/attachmentResume.vue

@@ -0,0 +1,77 @@
+<template>
+  <div>
+    <div class="d-flex attachment-item mb-2 cursor-pointer" v-for="k in attachmentList" :key="k.id">
+      <v-icon color="primary">mdi-file-account</v-icon>
+      <div class="file-name ellipsis ml-2">{{ k.title }}</div>
+      <v-icon color="primary" @click="previewFile(k.url)">mdi-eye-outline</v-icon>
+      <v-icon class="mx-2" color="primary" @click="handleDownload(k)">mdi-download-box-outline</v-icon>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import { previewFile } from '@/utils'
+defineOptions({name: 'enterprise-talentPool-details-attachmentResume'})
+
+const attachmentList = ref([
+  {
+    id: "1797473178549903362", 
+    title: "林同学-Java-15875754758.docx", 
+    url: "http://menduner.citupro.com:6868/admin-api/infra/file/24/get/725800ca45aced6c9742e94f576051c8c2527cd49b332c15be2dfbf17daedb92.docx", 
+    createTime: 1717385976000, 
+    updateTime: 1717385976000
+  }, 
+  {
+    id: "1797484319040229377", 
+    title: "林同学-C++-15875754758.docx", 
+    url: "http://menduner.citupro.com:6868/admin-api/infra/file/24/get/725800ca45aced6c9742e94f576051c8c2527cd49b332c15be2dfbf17daedb92.docx", 
+    createTime: 1717388632000, 
+    updateTime: 1717388632000
+  }, 
+  {
+    id: "1797511196383563778", 
+    title: "林同学-PHP-15875754758.docx", 
+    url: "http://menduner.citupro.com:6868/admin-api/infra/file/24/get/725800ca45aced6c9742e94f576051c8c2527cd49b332c15be2dfbf17daedb92.docx", 
+    createTime: 1717395040000, 
+    updateTime: 1717395040000
+  }
+])
+
+const getBlob = (url) => {
+  return new Promise(resolve => {
+    const xhr = new XMLHttpRequest()
+    xhr.open('GET', url, true)
+    xhr.responseType = 'blob'
+    xhr.onload = () => {
+      if (xhr.status === 200) resolve(xhr.response)
+    }
+    xhr.send()
+  })
+}
+
+const saveAs = (blob, filename) => {
+  var link = document.createElement('a')
+  link.href = window.URL.createObjectURL(blob)
+  link.download = filename
+  link.click()
+}
+// 下载附件
+const handleDownload = (k) => {
+  getBlob(k.url).then(blob => {
+    saveAs(blob, k.title)
+  })
+}
+</script>
+<style lang="scss" scoped>
+  .attachment-item {
+    color: #555;
+    font-size: 14px;
+    .file-name {
+      width: 230px;
+      &:hover {
+        color: var(--v-primary-base);
+      }
+    }
+  }
+</style>

+ 123 - 0
src/views/recruit/enterprise/talentPool/components/details/baseInfo.vue

@@ -0,0 +1,123 @@
+<!-- 基本信息 -->
+<template>
+  <div class="d-flex">
+    <!-- 头像 -->
+    <div class="avatarsBox">
+      <v-badge 
+        bordered 
+        offset-x="-25" 
+        offset-y="33" 
+        :color="info?.sex ? (info?.sex === '1' ? '#1867c0' : 'error') : 'error'" 
+        :icon="info?.sex ? (info?.sex === '1' ? 'mdi-gender-male' : 'mdi-gender-female') : 'mdi-gender-female'">
+        <v-avatar size=80 :image="info?.avatar || 'https://minio.citupro.com/dev/menduner/7.png'"></v-avatar>
+      </v-badge>
+    </div>
+    <!-- 信息 -->
+    <div style="flex: 1;">
+      <span style="font-size: 20px; font-weight: 600;color: var(--color-666);">{{ info?.name }}</span>
+      <div class="mt-3 d-flex">
+        <div class="listBox" style="height: 68px">
+          <div>
+            <span class="mdi mdi-map-marker-outline"></span>
+            <span>{{ info?.areaName || '暂无' }}</span>
+          </div>
+          <div>
+            <span class="mdi mdi-phone-outline"></span>
+            <span>{{ info?.phone }}</span>
+          </div>
+          <div>
+            <span class="mdi mdi-email-outline"></span>
+            <span>{{ info?.email }}</span>
+          </div>
+          <div>
+            <span class="mdi mdi-calendar-blank-outline"></span>
+            <span>{{ info?.expName }}</span>
+          </div>
+          <div>
+            <span class="mdi mdi-school-outline"></span>
+            <span>{{ info?.eduName }}</span>
+          </div>
+          <div>
+            <span class="mdi mdi-tag-outline"></span>
+            <span>{{ info?.jobStatusName }}</span>
+          </div>
+          <div>
+            <span class="mdi mdi-cake-variant-outline"></span>
+            <span>{{ timesTampChange(info?.birthday, 'Y-M-D') }}</span>
+          </div>
+          <div>
+            <span class="mdi mdi-account-heart"></span>
+            <span>{{ info?.maritalStatusName }}</span>
+          </div>
+          <div>
+            <span>{{ $t('resume.firstWorkTime') }}:</span>
+            <span>{{ timesTampChange(info?.firstWorkTime, 'Y-M-D') }}</span>
+          </div>
+        </div>
+      </div>
+      <div class="mt-4">
+        <span style="font-size: 15px;">个人画像:</span>
+        <v-chip size="small" label v-for="(k, i) in ['响应', '改变', '诚信', '进取精神', '信任', '卓越']" :key="i" class="mr-2" color="primary">{{ k }}</v-chip>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({name: 'enterprise-talentPool-details-baseInfo'})
+import { ref } from 'vue'
+import { timesTampChange } from '@/utils/date'
+import { dealDictObjData } from '@/utils/position'
+
+const props = defineProps({
+  data: Object
+})
+const info = ref({})
+if (props.data && Object.keys(props.data).length) {
+  info.value = dealDictObjData({}, props.data)
+}
+</script>
+
+<style lang="scss" scoped>
+.avatarsBox {
+  height: 80px;
+  width: 80px;
+  position: relative;
+  // margin: 32px;
+  margin-right: 40px;
+  .img {
+    width: 100%;
+    height: 100%;
+  }
+  .mdi {
+    font-size: 42px;
+    color: #fff;
+  }
+  div {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    border-radius: 50%;
+  }
+}
+.listBox {
+  display: flex;
+  flex-wrap: wrap; /* 允许换行 */
+  width: 100%; /* 设置容器宽度 */
+  // height: 68px;
+  overflow: hidden;
+  color: var(--color-666);
+  div {
+    margin-right: 50px;
+    span {
+      height: 32px;
+      line-height: 32px;
+    }
+    .mdi {
+      font-size: 22px;
+      margin-right: 8px;
+    }
+  }
+}
+</style>

+ 75 - 0
src/views/recruit/enterprise/talentPool/components/details/dict.js

@@ -0,0 +1,75 @@
+import { reactive } from 'vue'
+import { getDict } from '@/hooks/web/useDictionaries'
+
+const dictObj = reactive({})
+const dictList = [
+  { 
+    type: 'positionData', 
+    apiType: 'positionData', 
+    key: 'positionId', 
+    label: 'position', 
+    value: 'positionTypeData', 
+    itemKey: 'id', 
+    itemText: 'nameCn'
+  },
+  { 
+    type: 'industryList', 
+    apiType: 'industryList', 
+    key: 'industryIdList', 
+    label: 'industry', 
+    isArray: true, 
+    value: 'industryTypeData', 
+    itemKey: 'id', 
+    itemText: 'nameCn' 
+  },
+  { 
+    type: 'menduner_area_type',
+    apiType: 'areaList',
+    key: 'workAreaId',
+    label: 'workArea',
+    value: 'areaTypeData',
+    itemKey: 'id',
+    itemText: 'name'
+  },
+  { 
+    type: 'menduner_job_type',
+    key: 'jobType',
+    label: 'jobTypeName',
+    value: 'jobTypeData',
+    itemKey: 'value',
+    itemText: 'label'
+  }
+]
+
+// 字典
+const getDictList = async () => {
+  dictList.forEach(async (val) => {
+    const { data } = await getDict(val.type, val.params, val.apiType)
+    dictObj[val.value] = data
+  })
+}
+
+const getData = async () => {
+  await getDictList()
+}
+getData()
+
+export const dealJobData = (list) => {
+  let res = {}
+  dictList.forEach(item => {
+    res = list.map(e => {
+      let obj = {}
+      if (item.isArray) {
+        e[item.label] = e[item.key].map(val => {
+          return obj = dictObj[item.value].find(i => i[item.itemKey] === val)
+        })
+      } else {
+        obj = dictObj[item.value].find(k => Number(k[item.itemKey]) === Number(e[item.key]))
+        if (!obj) return
+        e[item.label] = obj[item.itemText]
+      }
+      return e
+    })
+  })
+  return res
+}

+ 64 - 0
src/views/recruit/enterprise/talentPool/components/details/educationExp.vue

@@ -0,0 +1,64 @@
+<template>
+  <div>
+    <div
+      v-for="(item, index) in dataList" :key="'educationExp' + index"
+      :class="[' mx-n2', {'mt-5': index }]"
+    >
+      <div class="educExpItem">
+        <div class="level1 d-flex align-center justify-space-between">
+          <div>
+            <span style="font-size: 16px;">{{ item.schoolName }}</span>
+            <span class="color6 font15 ml-5">
+              <span>{{ timesTampChange(item.startTime, 'Y-M') }}</span>
+              <span class="mx-1">至</span>
+              <span>{{ timesTampChange(item.endTime, 'Y-M') }}</span>
+            </span>
+          </div>
+          <div v-if="item.active">
+            <v-btn  variant="text" color="primary" prepend-icon="mdi-square-edit-outline" @click="handle(item)">{{ $t('common.edit') }}</v-btn>
+            <v-btn  variant="text" color="primary" prepend-icon="mdi-trash-can-outline" @click="handleDelete(item)">{{ $t('common.delete') }}</v-btn>
+          </div>
+        </div>
+        <div class="level2 my-2">
+          <span class="color6 font15">{{ item.major }}</span>
+          <span class="septal-line"  v-if="item.educationSystemType"></span>
+          <span class="color6 font15">{{ getText(item.educationSystemType, dictItemsObj.educationSystemType) }}</span>
+        </div>
+        <div class="level3">
+          <span class="color6 font15">在校经历:{{ item.content }}</span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({name: 'enterprise-talentPool-details-educationExp'})
+import { reactive, ref  } from 'vue'
+import { timesTampChange } from '@/utils/date'
+import { getText } from '@/utils/getText'
+import { getDict } from '@/hooks/web/useDictionaries'
+
+const props = defineProps({
+  data: Array
+})
+
+const dataList = ref([])
+if (props.data?.length) dataList.value = props.data
+
+// 获取字典内容
+const dictList = [
+  { type: 'menduner_education_system_type', key: 'educationSystemType' }
+]
+const dictItemsObj = reactive({})
+const getDictData = async (obj) => {
+  const { data } = await getDict(obj.type)
+  dictItemsObj[obj.key] = data || []
+}
+const getOptions = () => {
+  dictList.forEach(obj =>  getDictData(obj))
+}
+getOptions()
+</script>
+<style lang="scss" scoped>
+</style>

+ 58 - 0
src/views/recruit/enterprise/talentPool/components/details/jobIntention.vue

@@ -0,0 +1,58 @@
+<template>
+  <div v-if="interestList.length">
+    <div
+      :class="['position-item', 'mb-1']" 
+      v-for="(k, i) in interestList" 
+      :key="'jobIntention' + i"
+    >
+      <div class="d-flex">
+        <div>{{ k.position }}</div>
+        <div class="line">|</div>
+        <div v-if="!k.payFrom && !k.payTo">面议</div>
+        <div v-else>{{k.payFrom ? k.payFrom + '-' : ''}}{{k.payTo}}</div>
+        <div class="line">|</div>
+        <div class="grey-text text-box">{{ k.industry?.map(e => e.nameCn).join('、') }}</div>
+        <div class="line">|</div>
+        <div class="grey-text">{{ k.jobTypeName }}</div>
+        <div class="line" v-if="k.jobTypeName && k.workArea">|</div>
+        <div class="grey-text">{{ k.workArea }}</div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import { dealJobData } from './dict'
+defineOptions({name: 'enterprise-talentPool-details-jobIntention'})
+
+const props = defineProps({
+  data: {
+    type: Array,
+    default: () => []
+  }
+})
+
+// 获取求职意向
+const interestList = ref([])
+if (props.data?.length) interestList.value = dealJobData(props.data)
+
+</script>
+<style lang="scss" scoped>
+.position-item {
+  display: flex;
+  justify-content: space-between;
+  border-radius: 6px;
+  font-size: 15px;
+  span {
+    font-size: 15px;
+  }
+  .grey-text {
+    color: var(--color-999);
+  }
+  .line {
+    color: #e0e0e0;
+    margin: 0 10px;
+  }
+}
+</style>

+ 45 - 0
src/views/recruit/enterprise/talentPool/components/details/projectExperience.vue

@@ -0,0 +1,45 @@
+<template>
+  <div>
+    <div v-for="(k, i) in dataList" :key="i" class="exp mx-n2" @mouseenter="k.active = true" @mouseleave="k.active = false">
+      <div class="d-flex align-center justify-space-between">
+        <div>
+          <span style="font-size: 16px">{{ k.name }}</span>
+          <span class="label-title ml-5">
+            <span>{{ timesTampChange(k.startTime, 'Y-M') }}</span>
+            <span class="mx-1">至</span>
+            <span>{{ timesTampChange(k.endTime, 'Y-M') }}</span>
+          </span>
+        </div>
+      </div>
+      <div>
+        <span class="label-title">项目描述:</span>
+        <span class="label-title">{{ k.content }}</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({name: 'enterprise-talentPool-details-projectExperience'})
+import { timesTampChange } from '@/utils/date'
+import { ref } from 'vue'
+
+const props = defineProps({
+  data: Array
+})
+
+const dataList = ref([])
+if (props.data?.length) dataList.value = props.data
+
+</script>
+<style lang="scss" scoped>
+.exp {
+  font-size: 15px;
+  // cursor: pointer;
+  border-radius: 6px;
+  padding: 2px 10px 8px;
+  // &:hover {
+  //   background-color: var(--color-f8);
+  // }
+}
+</style>

+ 44 - 0
src/views/recruit/enterprise/talentPool/components/details/trainingExperience.vue

@@ -0,0 +1,44 @@
+<template>
+  <div v-if="dataList.length">
+    <div v-for="(k, i) in dataList" :key="i" class="exp mx-n2">
+      <div class="level1 d-flex align-center justify-space-between">
+        <div>
+          <span style="font-size: 16px;">{{ k.orgName }}</span>
+          <span class="label-title ml-5">
+            <span>{{ timesTampChange(k.startTime, 'Y-M') }}</span>
+            <span class="mx-1">至</span>
+            <span>{{ timesTampChange(k.endTime, 'Y-M') }}</span>
+          </span>
+        </div>
+      </div>
+      <div class="my-2">
+        <span class="label-title">培训课程:{{ k.course }}</span>
+      </div>
+      <div>
+        <span class="label-title">培训描述:</span>
+        <span class="label-title">{{ k.content }}</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({name: 'enterprise-talentPool-details-trainingExperience'})
+import { ref  } from 'vue'
+import { timesTampChange } from '@/utils/date'
+
+const props = defineProps({
+  data: Array
+})
+
+const dataList = ref([])
+if (props.data?.length) dataList.value = props.data
+
+</script>
+<style lang="scss" scoped>
+.exp {
+  font-size: 15px;
+  border-radius: 6px;
+  padding: 2px 10px 8px;
+}
+</style>

+ 51 - 0
src/views/recruit/enterprise/talentPool/components/details/vocationalSkills.vue

@@ -0,0 +1,51 @@
+<template>
+  <div>
+    <div 
+      :class="['dataList-item']" 
+      v-for="(k, i) in dataList" 
+      :key="i" 
+    >
+      <span >{{ getText(k.skillId, skillList, 'nameCn', 'id') }}</span>
+      <span class="septal-line"></span>
+      <span style="color: var(--color-999);">{{ getText(k.level, skillLevelArr) }}</span>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { getText } from '@/utils/getText'
+import { getDict } from '@/hooks/web/useDictionaries'
+import { ref } from 'vue'
+defineOptions({name: 'enterprise-talentPool-details-vocationalSkills'})
+const props = defineProps({
+  data: Array
+})
+
+const dataList = ref([])
+if (props.data?.length) dataList.value = props.data
+
+const skillLevelArr = ref([])
+getDict('menduner_skill_level').then(({ data }) => { // 字典
+  data = data?.length && data || []
+  skillLevelArr.value = data
+})
+
+const skillList = ref([])
+getDict('skillList', {}, 'skillList').then(({ data }) => { // 字典
+  data = data?.length && data || []
+  skillList.value = data
+})
+
+
+</script>
+<style lang="scss" scoped>
+.dataList-item {
+  border-radius: 6px;
+  span {
+    font-size: 15px;
+  }
+  .grey-text {
+    color: var(--color-999);
+  }
+}
+</style>

+ 51 - 0
src/views/recruit/enterprise/talentPool/components/details/workExperience.vue

@@ -0,0 +1,51 @@
+<!-- 工作经历 -->
+<template>
+  <div>
+    <div
+      v-for="(item, index) in dataList" :key="'workExperience' + index"
+      :class="[' mx-n2', {'mt-3': index }]"
+    >
+      <div class="educExpItem">
+        <div class="level1 d-flex align-center justify-space-between mb-2">
+          <div>
+            <span>{{ item.enterpriseName }}</span>
+            <span class="color6 font15 ml-5">
+              <span>{{ timesTampChange(item.startTime, 'Y-M') }}</span>
+              <span class="mx-1">至</span>
+              <span>{{ item.endTime ? timesTampChange(item.endTime, 'Y-M') : $t('sys.soFar') }}</span>
+            </span>
+            <span class="color6 font15 ml-5">{{ item.positionName }}</span>
+          </div>
+        </div>
+        <div class="level3">
+          <span class="color6 font15">工作内容:{{ item.content }}</span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({name: 'enterprise-talentPool-details-workExperience'})
+import { timesTampChange } from '@/utils/date'
+import { ref } from 'vue'
+
+const props = defineProps({
+  data: Array
+})
+
+const dataList = ref([])
+if (props.data?.length) dataList.value = props.data
+
+</script>
+
+<style lang="scss" scoped>
+.font15 { font-size: 15px;; }
+.color9 { color: var(--color-999); }
+.color6 { color: var(--color-666); }
+.color3 { color: var(--color-333); }
+.educExpItem {
+  border-radius: 6px;
+  padding: 2px 10px 8px;
+}
+</style>

+ 383 - 0
src/views/recruit/enterprise/talentPool/components/filter.vue

@@ -0,0 +1,383 @@
+<template>
+  <div class="px-5 pt-1 py-15" style="position: relative;">
+    <h3 class="my-3" style="color: var(--v-primary-base);">条件筛选</h3>
+    <!-- <v-divider class="my-3"></v-divider>
+    <div class="text-right reset-text cursor-pointer" @click="handleReset">重置筛选</div> -->
+    <CtForm ref="CtFormRef" :items="formItems" style="width: 100%;">
+      <!-- 期望岗位 -->
+      <template #positionId="{ item }">
+        <v-menu :close-delay="1" :open-delay="0" v-bind="$attrs">
+          <template v-slot:activator="{  props }">
+            <textUI
+              v-model="item.value"
+              :item="item"
+              v-bind="props"
+              style="position: relative;"
+              @handleClear="handleJobClickItem()"
+            ></textUI>
+          </template>
+          <jobTypeCard class="jobTypeCardBox" :select="[item[item.valueKey]].filter(Boolean)" :isSingle="true" @handleJobClick="handleJobClickItem"></jobTypeCard>
+        </v-menu>
+      </template>
+    </CtForm>
+    <div class="bottom">
+      <v-divider></v-divider>
+      <div class="d-flex justify-space-between mt-3">
+        <div class="ml-3">
+          <v-btn class="half-button mr-3" color="primary" @click="confirm">筛选</v-btn>
+          <v-btn class="half-button mr-3" variant="tonal" @click="emit('cancel')">取消</v-btn>
+        </div>
+        <v-btn class="half-button ml-3" variant="tonal" color="orange" @click="handleReset">重置</v-btn>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'talent-pool-filter'})
+import { getRocketLabelList } from '@/api/recruit/enterprise/resumeManagement/talentMap'
+
+import jobTypeCard from '@/components/jobTypeCard'
+import textUI from '@/components/FormUI/TextInput'
+import { getDict } from '@/hooks/web/useDictionaries'
+import Snackbar from '@/plugins/snackbar'
+import { ref } from 'vue'
+const emit = defineEmits(['cancel', 'confirm'])
+
+const CtFormRef = ref()
+const formItems = ref({
+  options: [
+    {
+      type: 'text',
+      key: 'name',
+      value: '',
+      label: '姓名 ',
+      col: 12
+    },
+    {
+      type: 'autocomplete',
+      key: 'sex',
+      value: null,
+      default: null,
+      label: '工作经验',
+      outlined: true,
+      itemText: 'label',
+      itemValue: 'value',
+      dictTypeName: 'menduner_sex',
+      items: []
+    },
+    // {
+    //   type: 'ifRadio',
+    //   key: 'sex',
+    //   value: '0',
+    //   default: 0,
+    //   label: '人才分类',
+    //   width: 90,
+    //   items: [
+    //     { label: '不限', value: '0' },
+    //     { label: '默认分类', value: '1' }
+    //   ]
+    // },
+    // {
+    //   type: 'ifRadio',
+    //   key: 'type',
+    //   value: '0',
+    //   default: 0,
+    //   label: '简历状态',
+    //   width: 120,
+    //   items: [
+    //     { label: '不限', value: '0' },
+    //     { label: '已查看', value: '1' },
+    //     { label: '未查看', value: '2' },
+    //     { label: '已导出', value: '3' },
+    //     { label: '已邀请', value: '4' },
+    //     { label: '已回复', value: '5' },
+    //     { label: '有评语', value: '6' }
+    //   ],
+    // },
+    {
+      type: 'text',
+      key: 'email',
+      value: '',
+      label: '联系邮箱 ',
+    },
+    {
+      type: 'phoneNumber',
+      key: 'phone',
+      value: '',
+      label: '联系手机号 ',
+    },
+    {
+      type: 'text',
+      key: 'wxCode',
+      value: '',
+      label: '微信号 ',
+    },
+    // {
+    //   type: 'text',
+    //   key: 'birthday',
+    //   value: '',
+    //   label: '出生日期 ',
+    // },
+    // {
+    //   type: 'autocomplete',
+    //   key: 'labels',
+    //   value: null,
+    //   label: '人员标签 ',
+    //   multiple: true,
+    //   outlined: true,
+    //   itemText: 'label',
+    //   itemValue: 'value',
+    //   items: [
+    //     { label: '标签', value: '0' },
+    //   ]
+    // },
+    // {
+    //   type: 'number',
+    //   key: 'age1',
+    //   value: null,
+    //   label: '年龄区间:起始',
+    // },
+    // {
+    //   type: 'number',
+    //   key: 'age2',
+    //   value: null,
+    //   label: '年龄区间:结束',
+    // },
+    // {
+    //   type: 'number',
+    //   key: 'pay1',
+    //   value: null,
+    //   suffix: '元',
+    //   label: '期望薪资:起始',
+    // },
+    // {
+    //   type: 'number',
+    //   key: 'pay2',
+    //   value: null,
+    //   suffix: '元',
+    //   label: '期望薪资:结束',
+    // },
+    {
+      type: 'areaSelect',
+      key: 'areaIds',
+      value: '',
+      // label: ' ',
+      placeholder: '所在城市', // 暂时只能使用placeholder
+      readonly: true,
+      limit: 1,
+    },
+    {
+      type: 'areaSelect',
+      key: 'regIds',
+      value: '',
+      // label: ' ',
+      placeholder: '户籍所在地', // 暂时只能使用placeholder
+      readonly: true,
+      limit: 1,
+    },
+    {
+      type: 'autocomplete',
+      key: 'eduType',
+      value: null,
+      default: null,
+      label: '学历',
+      outlined: true,
+      itemText: 'label',
+      itemValue: 'value',
+      dictTypeName: 'menduner_education_type',
+      items: []
+    },
+    {
+      type: 'autocomplete',
+      key: 'expType',
+      value: null,
+      default: null,
+      label: '工作经验',
+      outlined: true,
+      itemText: 'label',
+      itemValue: 'value',
+      dictTypeName: 'menduner_exp_type',
+      items: []
+    },
+    {
+      slotName: 'positionId',
+      key: 'positionName',
+      value: '',
+      label: '职位类型 ',
+      valueKey: 'positionId', 
+      hideDetails: true,
+      readonly: true,
+      outlined: true,
+    },
+    {
+      type: 'autocomplete',
+      key: 'jobType',
+      value: null,
+      label: '求职类型 ',
+      outlined: true,
+      itemText: 'label',
+      itemValue: 'value',
+      items: [
+        { label: '全职', value: '0' },
+        { label: '兼职', value: '1' },
+        { label: '临时', value: '2' },
+        { label: '实习', value: '3' }
+      ]
+    },
+    {
+      type: 'autocomplete',
+      key: 'jobStatus',
+      value: null,
+      default: null,
+      label: '求职状态 ',
+      outlined: true,
+      itemText: 'label',
+      itemValue: 'value',
+      dictTypeName: 'menduner_job_status',
+      items: []
+    },
+    {
+      type: 'autocomplete',
+      key: 'maritalStatus',
+      value: null,
+      default: null,
+      label: '婚姻状况 ',
+      outlined: true,
+      itemText: 'label',
+      itemValue: 'value',
+      dictTypeName: 'menduner_marital_status',
+      items: []
+    },
+  ]
+})
+
+// 获取字典内容
+const getDictData = async (item) => {
+  if (item) {
+    const { data } = await getDict(item.dictTypeName)
+    item.items = data
+  }
+}
+
+const getLabelData = async () => {
+  const res = await getRocketLabelList({ current: 1, size:9999, type: 'person' }) //type: job  enterprise person
+  const labels = res?.records || []
+  const labelsItem = formItems.value.options.find(f => f.key === 'labels')
+  if (labelsItem) {
+    labelsItem.items = labels.map(e => ({ label: e, value: e }))
+  }
+}
+getLabelData()
+
+let flexStyle = false
+formItems.value.options.forEach(e => {
+  // 查字典set options
+  if (e.dictTypeName) getDictData(e)
+  // 清除
+  e.clearable = true
+  // 样式
+  if (!e.col) {
+    e.col = 6
+    flexStyle = !flexStyle
+  } else {
+    e.flexStyle = 'mb-3'
+  }
+  if (flexStyle) e.flexStyle = 'mr-3'
+})
+
+// const setOneValue = (key, value) => {
+//   formItems.value.options.find(e => e.key === key).value = value
+// }
+
+// 期望职位
+const positionId = ref('')
+const positionName = ref('')
+const handleJobClickItem = (list, name) => {
+  const positionItem = formItems.value.options.find(f => f.key === 'positionName')
+  if (positionItem) {
+    if (list?.length) {
+      positionItem.value = positionName.value = name || ''
+      positionItem[positionItem.valueKey] = positionId.value = list?.length ? list[0] : ''
+    } else {
+      positionItem.value = name || ''
+      positionItem[positionItem.valueKey] = list?.length ? list[0] : ''
+    }
+  }
+}
+
+const confirm = () => {
+  const obj = {}
+  formItems.value.options.forEach(e => {
+    if (e.key === 'positionName') {
+      if (e.value) {
+        obj[e.valueKey] = positionId.value || ''
+        obj[e.key] = positionName.value || ''
+      }
+    } else if (e.value !== null && e.value !== '' && e.value !== undefined) obj[e.key] = e.value
+  })
+  if (obj.age1 > obj.age2) return Snackbar.warning('年龄区间异常,前者不能大于后者!')
+  if (obj.pay2 > obj.pay1) return Snackbar.warning('期望薪资异常,前者不能大于后者!')
+  //
+  if (obj.age1 || obj.age2) obj.age = [obj.age1 || null, obj.age2 || null].filter(Boolean)
+  if (obj.pay1 || obj.pay2) obj.pay = [obj.pay1 || null, obj.pay2 || null].filter(Boolean)
+  //
+  if (!obj.labels?.length) delete obj.labels
+  emit('confirm', obj)
+}
+
+const resetValue = () => {
+  formItems.value.options.forEach(e => {
+    if (e.key === 'positionName') {
+      e[e.valueKey] = ''
+      e.value = e[e.valueKey] = positionId.value = positionName.value = ''
+    } else {
+      e.value = null
+    }
+  })
+}
+const setValue = (query) => {
+  formItems.value.options.forEach(e => {
+    if (e.key === 'positionName') {
+      e.value = positionName.value = query[e.key]
+      e[e.valueKey] = positionId.value = query[e.valueKey]
+    } else {
+      if (query[e.key] !== undefined) e.value = query[e.key]
+    }
+  })
+}
+
+const handleReset = () => {
+  resetValue()
+  emit('confirm', {})
+}
+
+defineExpose({
+  setValue,
+  resetValue,
+})
+</script>
+
+<style scoped lang="scss">
+.reset-text {
+  font-size: 14px;
+  color: var(--color-666);
+  &:hover {
+    color: var(--v-primary-base);
+  }
+}
+.jobTypeCardBox {
+  position: absolute;
+  // top: -22px;
+  left: 0;
+}
+.bottom {
+  position: fixed;
+  width: 96%;
+  margin: 0 12px;
+  bottom: 0;
+  left: 0;
+  padding-bottom: 14px;
+  background-color: #fff;
+}
+</style>

+ 355 - 0
src/views/recruit/enterprise/talentPool/index copy.vue

@@ -0,0 +1,355 @@
+<!-- 样式备份 -->
+<template>
+  <v-card class="card-box pa-5">
+    <div class="d-flex justify-space-between">
+      <TextUI v-if="showTextUI" :item="textItem" @enter="handleEnter" @appendInnerClick="handleEnter"></TextUI>
+      <div></div>
+      <v-btn color="primary" prependIcon="mdi-filter-multiple-outline" class="half-button" variant="tonal" @click="openDrawer">筛选</v-btn>
+    </div>
+    <div v-if="items.length && showTextUI" class="d-flex align-center" style="margin-left: 14px;">
+      <v-checkbox v-model="selectAll" :label="!selectAll ? '全选' : `已选中${selectList.length}条`" hide-details color="primary" @update:model-value="handleChangeSelectAll"></v-checkbox>
+      <v-btn class="ml-8" :disabled="!selectAll" color="primary" variant="tonal" size="small">邀请面试</v-btn>
+      <v-btn class="mx-3" :disabled="!selectAll" color="primary" variant="tonal" size="small">简历回复</v-btn>
+      <v-btn :disabled="!selectAll" color="primary" variant="tonal" size="small">批量导出</v-btn>
+      <v-btn class="mx-3" :disabled="!selectAll" color="primary" variant="tonal" size="small">踢出人才库</v-btn>
+      <v-btn :disabled="!selectAll" color="primary" variant="tonal" size="small">移动到回收站</v-btn>
+      <v-btn class="ml-3" :disabled="!selectAll" color="primary" variant="tonal" size="small">加入黑名单</v-btn>
+    </div>
+    <div v-if="items.length" class="mt-3">
+      <div v-for="val in items" :key="val.id" class="list-item mb-3">
+        <div class="top">
+          <v-checkbox class="mr-5" v-model="val.select" color="primary" density="compact" hide-details @update:model-value="handleChangeSelect"></v-checkbox>
+          <span>应聘/意向职位:{{ val.job.name }}</span>
+          <span class="mx-10">加入时间:{{ val.createTime }}</span>
+          <span>人才分类:{{ val.type }}</span>
+        </div>
+        <div @click.stop="talentPoolDetails(val)" class="px-5 py-3 d-flex justify-space-between align-center cursor-pointer">
+          <div class="d-flex">
+            <v-badge
+              bordered
+              offset-x="6"
+              offset-y="44"
+              :color="val.sex ? (val.sex === '1' ? '#1867c0' : 'error') : 'error'"
+              :icon="val.sex ? (val.sex === '1' ? 'mdi-gender-male' : 'mdi-gender-female') : 'mdi-gender-female'">
+              <v-avatar size="large" :image="val.avatar || 'https://minio.citupro.com/dev/menduner/7.png'"></v-avatar>
+            </v-badge>
+            <div class="ml-5">
+              <div class="user-name">{{ val.name }}</div>
+              <div class="mt-2 user-info">
+                <span v-for="(k, i) in dict" :key="k">
+                  {{ val[k] }}
+                  <span v-if="i !== dict.length - 1" class="mx-3">|</span>
+                </span>
+              </div>
+            </div>
+          </div>
+          <div>
+            <!-- <v-btn color="primary" variant="tonal" @click.stop="{}">和TA聊聊</v-btn>
+            <v-btn class="ml-3" color="primary" @click.stop="{}">邀请面试</v-btn> -->
+            <v-btn class="ml-3" color="primary" variant="tonal" @click.stop="handleRemove(val)">踢出人才库</v-btn>
+          </div>
+        </div>
+        <div class="d-flex mx-5 bottom cursor-pointer">
+          <div class="experience" v-if="val.exp.length">
+            <div class="second-title">工作经验</div>
+            <v-timeline density="compact" align="start" side="end" truncate-line="both">
+              <v-timeline-item v-for="(j, i) in val.exp" :key="i" dot-color="primary" size="small">
+                <div class="timeline-item mt-1">
+                  <div>{{ j.startTime }}-{{ j.endTime }} ({{ j.year }})</div>
+                  <div class="timeline-item-name ellipsis">{{ j.name }}</div>
+                  <div class="timeline-item-name ellipsis">{{ j.jobName }}</div>
+                </div>
+              </v-timeline-item>
+            </v-timeline>
+          </div>
+          <div class="edu" v-if="val.edu.length">
+            <div class="second-title">教育经历</div>
+            <v-timeline density="compact" align="start" side="end" truncate-line="both">
+              <v-timeline-item v-for="(j, i) in val.edu" :key="i" dot-color="primary" size="small">
+                <div class="timeline-item mt-1">
+                  <div>{{ j.startTime }}-{{ j.endTime }}</div>
+                  <div class="timeline-item-name ellipsis">{{ j.name }}</div>
+                  <div class="timeline-item-name ellipsis">{{ j.major }}</div>
+                </div>
+              </v-timeline-item>
+            </v-timeline>
+          </div>
+        </div>
+      </div>
+      <CtPagination
+        :total="total"
+        :page="pageInfo.pageNo"
+        :limit="pageInfo.pageSize"
+        @handleChange="handleChangePage"
+      ></CtPagination>
+    </div>
+    <Empty v-else :message="tipsText" :elevation="false" class="mt-15"></Empty>
+
+    <v-navigation-drawer v-model="screen" location="right" absolute temporary width="700">
+      <FilterPage
+        ref="FilterPageRef"
+        @confirm="handleConfirm"
+        @cancel="screen = false"
+      ></FilterPage>
+    </v-navigation-drawer>
+  </v-card>
+</template>
+
+<script setup>
+// import { useRouter } from 'vue-router'
+// const router = useRouter()
+defineOptions({ name: 'enterprise-talent-pool'})
+import { useI18n } from '@/hooks/web/useI18n'; const { t } = useI18n()
+import { reactive, ref } from 'vue'
+import Snackbar from '@/plugins/snackbar'
+import TextUI from '@/components/FormUI/TextInput'
+import FilterPage from './components/filter.vue'
+import { dealDictArrayData } from '@/utils/position'
+import { getTalentPoolPage } from '@/api/recruit/enterprise/talentPool'
+import { removeFormTalentPool } from '@/api/recruit/enterprise/personnel'
+
+
+let query = {}
+const showTextUI = ref(false)
+const screen = ref(false)
+const selectAll = ref(false)
+const selectList = ref([])
+const items = ref([
+  {
+    job: {
+      name: '客房服务员'
+    },
+    createTime: '2026-11-12',
+    type: '默认分类',
+    name: '花城',
+    age: '27岁',
+    expName: '3年经验',
+    areaName: '广州',
+    userId: '1',
+    id: '1793583467288223745',
+    sex: '2',
+    select: false,
+    eduName: '本科',
+    payName: '薪资面议',
+    avatar: 'https://cdn.vuetifyjs.com/images/john.jpg',
+    exp: [
+      {
+        startTime: '2016.05',
+        endTime: '2018.05',
+        year: '2年',
+        name: '广州辞图科技有限公司',
+        jobName: '前台'
+      },
+      {
+        startTime: '2016.05',
+        endTime: '2018.05',
+        year: '2年',
+        name: '广州辞图科技有限公司',
+        jobName: '前台'
+      }
+    ],
+    edu: [
+      {
+        startTime: '2016.05',
+        endTime: '2018.05',
+        name: '广州大学',
+        major: '酒店管理'
+      }
+    ]
+  },
+  {
+    job: {
+      name: '客房服务员'
+    },
+    createTime: '2026-11-12',
+    type: '默认分类',
+    name: '花城',
+    age: '27岁',
+    expName: '3年经验',
+    areaName: '广州',
+    userId: '1',
+    id: '1793583467288223745',
+    sex: '2',
+    select: false,
+    eduName: '本科',
+    payName: '薪资面议',
+    avatar: 'https://cdn.vuetifyjs.com/images/john.jpg',
+    exp: [
+      {
+        startTime: '2016.05',
+        endTime: '2018.05',
+        year: '2年',
+        name: '广州辞图科技有限公司',
+        jobName: '前台'
+      },
+      {
+        startTime: '2016.05',
+        endTime: '2018.05',
+        year: '2年',
+        name: '广州辞图科技有限公司',
+        jobName: '前台'
+      }
+    ],
+    edu: [
+      {
+        startTime: '2016.05',
+        endTime: '2018.05',
+        name: '广州大学',
+        major: '酒店管理'
+      }
+    ]
+  }
+])
+// items.value = [] // 暂定无数据展示
+const tipsText = ref('暂无数据')
+const total = ref(2)
+const pageInfo = reactive({ pageNo: 1, pageSize: 10 })
+
+const textItem = ref({
+  type: 'text',
+  width: 600,
+  value: '',
+  label: '请输入简历姓名/职位名称',
+  appendInnerIcon: 'mdi-magnify'
+})
+const dict = ['age', 'expName', 'areaName', 'eduName', 'payName']
+
+// 获取数据
+const getData = async () => {
+  const obj = { ...pageInfo, ...query }
+  console.log('obj', obj)
+  const { list, total: number } = await getTalentPoolPage(pageInfo)
+  total.value = number
+  if (showTextUI.value) items.value = list?.length ? dealDictArrayData([], list) : []
+}
+// getData()
+
+const handleEnter = (e) => {
+  console.log(e, 'enter')
+}
+
+// 移出人才库 
+const handleRemove = async (item) => {
+  if (!item.userId) return Snackbar.warning('数据异常')
+  await removeFormTalentPool(item.userId)
+  Snackbar.success(t('common.operationSuccessful'))
+}
+
+const dealSelect = () => {
+  selectList.value = items.value.filter(e => e.select).map(k => k.id)
+}
+
+// 全选
+const handleChangeSelectAll = () => {
+  items.value.map(k => {
+    k.select = selectAll.value
+    return k
+  })
+  dealSelect()
+}
+// 单选
+const handleChangeSelect = () => {
+  const length = items.value.filter(k => k.select).length
+  selectAll.value = length > 0 ? true : false
+  dealSelect()
+}
+
+const handleChangePage = () => {
+  // 分页获取新数据后勾选
+  selectList.value.forEach(e => {
+    const obj = items.value.find(k => k.id === e)
+    if (obj) obj.select = true
+  })
+  getData()
+}
+
+// 筛选
+const handleConfirm = (params) => {
+  screen.value = false
+  pageInfo.pageNo = 1
+  query = { ...params }
+  getData()
+}
+
+const FilterPageRef = ref()
+const openDrawer = () => {
+  screen.value = true
+  if (Object.keys(query).length) FilterPageRef.value?.setValue(query)
+  else FilterPageRef.value?.resetValue()
+}
+
+
+// 人才详情
+const talentPoolDetails = ({ userId, id }) => {
+  if (!userId || !id) return
+  window.open(`/recruit/enterprise/talentPool/details/${userId}?id=${id}`)
+}
+</script>
+
+<style scoped lang="scss">
+.list-item {
+  border: 1px solid #e5e6eb;
+}
+.top {
+  display: flex;
+  background-color: #f7f8fa;
+  height: 50px;
+  line-height: 50px;
+  font-size: 14px;
+  color: var(--color-666);
+  padding: 0 20px;
+}
+.user-name {
+  font-size: 18px;
+  font-weight: 700;
+  color: #555;
+}
+.user-info {
+  color: var(--color-666);
+  font-size: 14px;
+  font-weight: 500;
+}
+:deep(.v-timeline-divider__dot--size-small) {
+  width: 10px !important;
+  height: 10px !important;
+  margin-top: 10px !important;
+}
+:deep(.v-timeline-divider__inner-dot) {
+  width: 10px !important;
+  height: 10px !important;
+}
+.bottom {
+  display: flex;
+  justify-content: space-between;
+  padding-bottom: 12px;
+  .experience {
+    width: 54%;
+    height: 100%;
+  }
+  .edu {
+    width: 40%;
+    height: 100%;
+  }
+}
+.second-title {
+  color: var(--color-666);
+  font-size: 15px;
+}
+.timeline-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  width: 100%;
+  color: var(--color-666);
+  font-size: 13px;
+  .timeline-item-name {
+    width: 26%;
+  }
+}
+:deep(.v-timeline-item__body) {
+  width: 100%;
+}
+:deep(.v-timeline--vertical.v-timeline) {
+  row-gap: 0;
+}
+</style>

+ 219 - 0
src/views/recruit/enterprise/talentPool/index.vue

@@ -0,0 +1,219 @@
+<template>
+  <v-card class="card-box pa-5">
+    <div class="d-flex justify-space-between">
+      <div></div>
+      <v-btn color="primary" prependIcon="mdi-filter-multiple-outline" class="half-button" variant="tonal" @click="openDrawer">筛选</v-btn>
+    </div>
+    <div v-if="dataList?.length" class="mt-3">
+      <v-data-table
+        class="mt-3"
+        :items="dataList"
+        :headers="headers"
+        hover
+        :disable-sort="true"
+        item-value="id"
+      >
+        <template #bottom></template>
+        <template v-slot:[`item.name`]="{ item }">
+          <div class="d-flex align-center cursor-pointer" @click="talentPoolDetails(item)">
+            <v-badge
+              bordered
+              offset-y="6"
+              :color="badgeColor(item)"
+              :icon="badgeIcon(item)">
+              <v-avatar size="40" :image="getUserAvatar(item.avatar, item.sex)"></v-avatar>
+            </v-badge>
+            <span class="defaultLink ml-3">{{ item?.name }}</span>
+          </div>
+        </template>
+        <template v-slot:[`item.advantage`]="{ item }">
+          <template v-if="item.advantage">
+            <v-btn color="primary" variant="tonal" @click="advantageDetail(item.advantage)">查看</v-btn>
+          </template>
+        </template>
+        <template v-slot:[`item.actions`]="{ item }">
+          <v-btn color="primary" variant="text" @click="handleRemove(item)">踢出人才库</v-btn>
+          <v-btn color="primary" variant="text" @click="talentPoolDetails(item)">人才详情</v-btn>
+        </template>
+      </v-data-table>
+      <CtPagination
+        :total="total"
+        :page="pageInfo.pageNo"
+        :limit="pageInfo.pageSize"
+        @handleChange="handleChangePage"
+      ></CtPagination>
+    </div>
+    <Empty v-else :message="tipsText" :elevation="false" class="mt-15"></Empty>
+
+    <v-navigation-drawer v-model="screen" location="right" absolute temporary width="700">
+      <FilterPage
+        ref="FilterPageRef"
+        @confirm="handleConfirm"
+        @cancel="screen = false"
+      ></FilterPage>
+    </v-navigation-drawer>
+  </v-card>
+</template>
+
+<script setup>
+// import { useRouter } from 'vue-router'
+// const router = useRouter()
+defineOptions({ name: 'enterprise-talent-pool'})
+import { useI18n } from '@/hooks/web/useI18n'; const { t } = useI18n()
+import { computed, reactive, ref } from 'vue'
+import Snackbar from '@/plugins/snackbar'
+import FilterPage from './components/filter.vue'
+import { dealDictArrayData } from '@/utils/position'
+import { getTalentPoolPage } from '@/api/recruit/enterprise/talentPool'
+import { removeFormTalentPool } from '@/api/recruit/enterprise/personnel'
+import { timesTampChange } from '@/utils/date'
+import { getUserAvatar } from '@/utils/avatar'
+
+
+let query = {}
+const screen = ref(false)
+const dataList = ref([])
+// dataList.value = [] // 暂定无数据展示
+const tipsText = ref('暂无数据')
+const total = ref(2)
+const pageInfo = reactive({ pageNo: 1, pageSize: 10 })
+
+
+// 获取数据
+const getData = async () => {
+  const obj = { ...pageInfo, ...query }
+  const { list, total: number } = await getTalentPoolPage(obj)
+  total.value = number
+  dataList.value = list?.length ? dealDictArrayData([], list) : []
+}
+getData()
+
+
+// 移出人才库 
+const handleRemove = async (item) => {
+  if (!item.userId) return Snackbar.warning('数据异常')
+  await removeFormTalentPool(item.userId)
+  Snackbar.success(t('common.operationSuccessful'))
+  getData()
+}
+
+const handleChangePage = (e) => {
+  pageInfo.pageNo = e
+  getData()
+}
+
+// 筛选
+const handleConfirm = (params) => {
+  screen.value = false
+  pageInfo.pageNo = 1
+  query = { ...params }
+  getData()
+}
+
+const FilterPageRef = ref()
+const openDrawer = () => {
+  screen.value = true
+  if (Object.keys(query).length) FilterPageRef.value?.setValue(query)
+  else FilterPageRef.value?.resetValue()
+}
+
+const headers = [
+  { title: '姓名', key: 'name', sortable: false },
+  { title: '求职状态', key: 'jobStatusName', sortable: false },
+  // { title: '求职类型', key: 'jobName', sortable: false },
+  { title: '电话号码', key: 'phone', sortable: false },
+  { title: '常用邮箱', key: 'email', sortable: false },
+  // { title: '微信二维码', key: 'wxCode', sortable: false },
+  { title: '出生日期', key: 'birthday', sortable: false, value: item =>  timesTampChange(item.birthday, 'Y-M-D') },
+  // { title: '加入时间', key: 'createTime', sortable: false, value: item =>  timesTampChange(item.createTime, 'Y-M-D') },
+  { title: '婚姻状况', key: 'maritalStatusName', sortable: false },
+  { title: '所在城市', key: 'areaName', sortable: false },
+  { title: '户籍地', key: 'regName', sortable: false },
+  { title: '首次工作时间', key: 'firstWorkTime', sortable: false, value: item =>  timesTampChange(item.firstWorkTime, 'Y-M-D') },
+  // { title: '个人优势', key: 'advantage', sortable: false },
+  { title: '工作年限', key: 'expName', sortable: false },
+  { title: '最高学历', key: 'eduName', sortable: false },
+  { title: '操作', value: 'actions', sortable: false }
+]
+const badgeColor = computed(() => (item) => {
+  return (item && item.sex) ? (item.sex === '1' ? '#1867c0' : 'error') : 'error'
+})
+
+const badgeIcon = computed(() => (item) => {
+  return (item && item.sex) ? (item.sex === '1' ? 'mdi-gender-male' : 'mdi-gender-female') : 'mdi-gender-female'
+})
+
+// 人才详情
+const talentPoolDetails = ({ userId, id }) => {
+  if (!userId || !id) return
+  window.open(`/recruit/enterprise/talentPool/details/${userId}?id=${id}`)
+}
+</script>
+
+<style scoped lang="scss">
+.list-item {
+  border: 1px solid #e5e6eb;
+}
+.top {
+  display: flex;
+  background-color: #f7f8fa;
+  height: 50px;
+  line-height: 50px;
+  font-size: 14px;
+  color: var(--color-666);
+  padding: 0 20px;
+}
+.user-name {
+  font-size: 18px;
+  font-weight: 700;
+  color: #555;
+}
+.user-info {
+  color: var(--color-666);
+  font-size: 14px;
+  font-weight: 500;
+}
+:deep(.v-timeline-divider__dot--size-small) {
+  width: 10px !important;
+  height: 10px !important;
+  margin-top: 10px !important;
+}
+:deep(.v-timeline-divider__inner-dot) {
+  width: 10px !important;
+  height: 10px !important;
+}
+.bottom {
+  display: flex;
+  justify-content: space-between;
+  padding-bottom: 12px;
+  .experience {
+    width: 54%;
+    height: 100%;
+  }
+  .edu {
+    width: 40%;
+    height: 100%;
+  }
+}
+.second-title {
+  color: var(--color-666);
+  font-size: 15px;
+}
+.timeline-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  width: 100%;
+  color: var(--color-666);
+  font-size: 13px;
+  .timeline-item-name {
+    width: 26%;
+  }
+}
+:deep(.v-timeline-item__body) {
+  width: 100%;
+}
+:deep(.v-timeline--vertical.v-timeline) {
+  row-gap: 0;
+}
+</style>

+ 1 - 1
src/views/recruit/enterprise/talentRecruitment/components/table.vue

@@ -117,7 +117,7 @@ watch(
 // 人才详情
 const handleToPersonDetail = ({ userId, id }) => {
   if (!userId || !id) return
-  window.open(`/recruit/enterprise/resumeManagement/talentPool/details/${userId}?id=${id}`)
+  window.open(`/recruit/enterprise/talentPool/details/${userId}?id=${id}`)
 }
 
 // 加入人才库