Browse Source

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

zhengnaiwen_citu 8 tháng trước cách đây
mục cha
commit
d48925cf27
100 tập tin đã thay đổi với 2463 bổ sung1932 xóa
  1. 3 2
      src/api/common/index.js
  2. 1 1
      src/api/enterprise.js
  3. 16 1
      src/api/recruit/enterprise/personnel/index.js
  4. 9 0
      src/api/recruit/enterprise/resumeManagement/talentPool/index.js
  5. 1 1
      src/components/Enterprise/components/introduction.vue
  6. 4 3
      src/components/Enterprise/components/positions.vue
  7. 4 2
      src/components/Enterprise/details.vue
  8. 9 5
      src/components/Enterprise/hotPromoted.vue
  9. 10 15
      src/components/Position/item.vue
  10. 4 4
      src/components/Position/longCompany.vue
  11. 7 6
      src/components/Position/longStrip.vue
  12. 2 1
      src/components/Position/similarPositions.vue
  13. 2 1
      src/components/PositionLongStrip/item.vue
  14. 9 9
      src/components/VerificationCode/index.vue
  15. 42 36
      src/components/headSearch/index.vue
  16. 0 9
      src/components/personalRecharge/index.vue
  17. 28 26
      src/config/axios/service.js
  18. 6 6
      src/layout/company/navBar.vue
  19. 14 13
      src/layout/personal/navBar.vue
  20. 1 1
      src/locales/en.js
  21. 1 1
      src/locales/zh-CN.js
  22. 3 2
      src/permission.js
  23. 7 19
      src/router/modules/common.js
  24. 7 0
      src/router/modules/components/headhunting.js
  25. 94 0
      src/router/modules/components/recruit/personCenter.js
  26. 24 65
      src/router/modules/components/recruit/personal.js
  27. 14 0
      src/router/modules/recruit.js
  28. 36 0
      src/router/modules/remaining.js
  29. 26 23
      src/store/user.js
  30. 10 12
      src/utils/auth.js
  31. 2 2
      src/utils/eventList.js
  32. 68 0
      src/utils/headhuntingData.js
  33. 0 12
      src/utils/position.js
  34. 3 3
      src/utils/statisticsHeaders.js
  35. 0 1
      src/views/common/loginDialog.vue
  36. 0 60
      src/views/headhunting copy/components/form.vue
  37. 0 209
      src/views/headhunting copy/index.vue
  38. 26 8
      src/views/headhunting/components/content.vue
  39. 3 3
      src/views/headhunting/components/serviceContent.vue
  40. 18 0
      src/views/headhunting/details.vue
  41. 4 1
      src/views/integral/pointsManagement/components/integralShow.vue
  42. 114 0
      src/views/login/components/editPassword.vue
  43. 19 18
      src/views/login/components/passwordPage.vue
  44. 38 0
      src/views/login/forgotPassword.vue
  45. 39 160
      src/views/login/index.vue
  46. 1 1
      src/views/publicRecruitment/components/table.vue
  47. 7 5
      src/views/recruit/components/message/components/chatting.vue
  48. 2 1
      src/views/recruit/components/message/index.vue
  49. 1 1
      src/views/recruit/entRegister/inReview.vue
  50. 1 1
      src/views/recruit/entRegister/joiningEnterprise.vue
  51. 59 21
      src/views/recruit/entRegister/register.vue
  52. 2 1
      src/views/recruit/enterprise/enterpriseCenter/components/positions.vue
  53. 2 1
      src/views/recruit/enterprise/hirePosition/components/item.vue
  54. 1 1
      src/views/recruit/enterprise/interviewManagement/index.vue
  55. 1 1
      src/views/recruit/enterprise/personnelManagement/components/invite.vue
  56. 1 1
      src/views/recruit/enterprise/personnelManagement/components/screen.vue
  57. 1 1
      src/views/recruit/enterprise/personnelManagement/components/table.vue
  58. 2 1
      src/views/recruit/enterprise/positionManagement/components/item.vue
  59. 1 1
      src/views/recruit/enterprise/resumeManagement/elite/components/invite.vue
  60. 1 1
      src/views/recruit/enterprise/resumeManagement/elite/components/public.vue
  61. 1 1
      src/views/recruit/enterprise/resumeManagement/elite/components/screen.vue
  62. 11 2
      src/views/recruit/enterprise/resumeManagement/elite/components/table.vue
  63. 311 0
      src/views/recruit/enterprise/resumeManagement/talentMap/components/filter copy.vue
  64. 22 211
      src/views/recruit/enterprise/resumeManagement/talentMap/components/filter.vue
  65. 7 5
      src/views/recruit/enterprise/resumeManagement/talentMap/index.vue
  66. 2 1
      src/views/recruit/enterprise/resumeManagement/talentPool/components/details/jobIntention.vue
  67. 295 102
      src/views/recruit/enterprise/resumeManagement/talentPool/components/filter.vue
  68. 355 0
      src/views/recruit/enterprise/resumeManagement/talentPool/index copy.vue
  69. 108 196
      src/views/recruit/enterprise/resumeManagement/talentPool/index.vue
  70. 1 1
      src/views/recruit/enterprise/systemManagement/groupAccount/inviteConfirm.vue
  71. 3 3
      src/views/recruit/enterprise/systemManagement/groupAccount/inviteConfirmEnt.vue
  72. 0 0
      src/views/recruit/personal/PersonalCenter/accountSettings/accountBinding.vue
  73. 41 0
      src/views/recruit/personal/PersonalCenter/accountSettings/editPassword.vue
  74. 0 0
      src/views/recruit/personal/PersonalCenter/accountSettings/privacySettings.vue
  75. 0 0
      src/views/recruit/personal/PersonalCenter/accountSettings/realAuthentication.vue
  76. 2 1
      src/views/recruit/personal/PersonalCenter/components/communication.vue
  77. 2 1
      src/views/recruit/personal/PersonalCenter/components/interviewSchedule.vue
  78. 0 187
      src/views/recruit/personal/PersonalCenter/dynamic/left.vue
  79. 0 207
      src/views/recruit/personal/PersonalCenter/dynamic/right.vue
  80. 103 14
      src/views/recruit/personal/PersonalCenter/index.vue
  81. 0 0
      src/views/recruit/personal/PersonalCenter/jobFeedback/components/companyCollection.vue
  82. 0 0
      src/views/recruit/personal/PersonalCenter/jobFeedback/components/delivery.vue
  83. 0 0
      src/views/recruit/personal/PersonalCenter/jobFeedback/components/interested.vue
  84. 0 0
      src/views/recruit/personal/PersonalCenter/jobFeedback/components/interview/index.vue
  85. 4 3
      src/views/recruit/personal/PersonalCenter/jobFeedback/components/interview/item.vue
  86. 0 0
      src/views/recruit/personal/PersonalCenter/jobFeedback/components/positionCollection.vue
  87. 2 2
      src/views/recruit/personal/PersonalCenter/jobFeedback/components/seenMe.vue
  88. 34 0
      src/views/recruit/personal/PersonalCenter/jobFeedback/index.vue
  89. 30 0
      src/views/recruit/personal/PersonalCenter/taskCenter/components/daily.vue
  90. 105 0
      src/views/recruit/personal/PersonalCenter/taskCenter/components/signIn.vue
  91. 30 0
      src/views/recruit/personal/PersonalCenter/taskCenter/components/suggest.vue
  92. 21 0
      src/views/recruit/personal/PersonalCenter/taskCenter/index.vue
  93. 33 0
      src/views/recruit/personal/PersonalCenter/wallet/index.vue
  94. 120 0
      src/views/recruit/personal/PersonalCenter/wallet/myBalance/index.vue
  95. 0 141
      src/views/recruit/personal/accountSettings/dynamic/editPassword.vue
  96. 0 67
      src/views/recruit/personal/accountSettings/index.vue
  97. 1 1
      src/views/recruit/personal/company/index.vue
  98. 5 3
      src/views/recruit/personal/home/components/hotJobs.vue
  99. 2 2
      src/views/recruit/personal/home/components/hotPromotedPositions.vue
  100. 1 1
      src/views/recruit/personal/home/components/popularEnterprises.vue

+ 3 - 2
src/api/common/index.js

@@ -83,8 +83,8 @@ export const updatePassword = async (data) => {
 
 // 重置密码
 export const resetPassword = async (data) => {
-  return await request.post({
-    url: '/admin-api/menduner/system/mde-user/update-password',
+  return await request.put({
+    url: '/app-api/menduner/system/mde-user/reset-password',
     data
   })
 }
@@ -213,6 +213,7 @@ export const userRefreshToken = async (refreshToken) => {
 export const getEnterpriseUserAccount = async () => {
   return await request.get({
     // url: '/app-api/menduner/system/recruit/user/get/account'
+    tokenIndex: 1, // 使用招聘token
     url: '/app-api/pay/currency/get' // 获取货币账户
   })
 }

+ 1 - 1
src/api/enterprise.js

@@ -156,7 +156,7 @@ export const updateEnterpriseTag = async (data) => {
   })
 }
 
-// 根据类型获取标签信息
+// 招聘端-根据类型获取标签信息
 export const getTagTreeDataApi = async (params) => {
   return await request.get({
     url: '/admin-api/menduner/system/tag/get/by/type',

+ 16 - 1
src/api/recruit/enterprise/personnel/index.js

@@ -35,4 +35,19 @@ export const personCvUnfitCancel = async (id) => {
   return await request.post({
     url: `/app-api/menduner/system/recruit/person-cv/unfit/cancellation?id=${id}`
   })
-}
+}
+
+// 招聘端-牛人管理-加入人才库
+export const joinToTalentPool = async (id) => {
+  return await request.post({
+    url: `/app-api/menduner/system/enterprise-talent-pool/add?userId=${id}`
+  })
+}
+
+// 招聘端-牛人管理-移出人才库
+export const removeFormTalentPool = async (id) => {
+  return await request.post({
+    url: `/app-api/menduner/system/enterprise-talent-pool/remove?userId=${id}`
+  })
+}
+

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

@@ -0,0 +1,9 @@
+import request from '@/config/axios'
+
+// 招聘端-牛人管理-人才库 - 分页查询企业人才库信息
+export const getTalentPoolPage = async (params) => {
+  return await request.get({
+    url: '/app-api/menduner/system/enterprise-talent-pool/page',
+    params
+  })
+}

+ 1 - 1
src/components/Enterprise/components/introduction.vue

@@ -6,7 +6,7 @@
     <v-divider class="my-3"></v-divider>
     <div>
       <h4>公司地址</h4>
-      <div v-if="props.info.business.address" class="mt-1">
+      <div v-if="props.info?.business?.address" class="mt-1">
         <v-icon size="25" color="primary">mdi-map-marker</v-icon>
         <span style="color: var(--color-666);font-size: 15px;">{{ props.info.business.address }}</span>
       </div>

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

@@ -1,6 +1,6 @@
 <template>
   <div class="top">
-    <div class="d-flex">
+    <div class="d-flex" v-if="positionCategory.length">
       <div class="font-weight-bold position-category-left">职位类别:</div>
       <div class="position-category-right">
         <span 
@@ -54,12 +54,13 @@
           </div>
         </div>
         <div v-if="!val.active" class="text-right">
-          <p class="salary">{{ val.job.payFrom }}-{{ val.job.payTo }}/{{ val.job.payName }}</p>
+          <p v-if="!val.job.payFrom && !val.job.payTo" class="salary">面议</p>
+          <p v-else class="salary">{{ val.job.payFrom ? val.job.payFrom + '-' : '' }}{{ val.job.payTo }}{{ val.job.payName ? '/' + val.job.payName : '' }}</p>
           <div class="update-time">{{ timesTampChange(val.job.updateTime) }} 刷新</div>
         </div>
         <div v-else class="account-info">
           <v-avatar :image="getUserAvatar(val.contact.avatar, val.contact.sex)"></v-avatar>
-          <span class="account-label">{{ val.contact.name }} · {{ val.contact.postNameCn }}</span>
+          <span class="account-label">{{ val.contact.name }}{{ val.contact.postNameCn ? ' · ' + val.contact.postNameCn : '' }}</span>
           <span>
             <v-btn class="half-button" color="primary" size="small" @click="toDetails(val)">立即沟通</v-btn>
           </span>

+ 4 - 2
src/components/Enterprise/details.vue

@@ -54,7 +54,7 @@
                 <v-chip size="small" label v-for="(k, i) in info?.enterprise?.welfareList?.slice(0, 6)" :key="i" class="mb-2 welfare-tags-item ellipsis" color="primary">{{ k }}</v-chip>
               </div>
             </div>
-            <div class="welfare">
+            <div class="welfare" v-if="info?.business">
               <h4>工商信息</h4>
               <div :class="['mt-2', 'business-item']" v-for="val in businessList" :key="val.value">
                 <div>{{ val.label }}</div>
@@ -123,7 +123,9 @@ const getDetails = async () => {
   if (!props.id) return
   const data = await getEnterpriseDetails({ id: props.id })
   // 成立日期
-  data.business.establishmentTime = timesTampChange(data.business.establishmentTime, 'Y-M-D')
+  if (data?.business?.establishmentTime) {
+    data.business.establishmentTime = timesTampChange(data.business.establishmentTime, 'Y-M-D')
+  }
 
   info.value = { ...data, ...dealDictObjData({}, data.enterprise) }
   getCollectionStatus(props.id)

+ 9 - 5
src/components/Enterprise/hotPromoted.vue

@@ -18,7 +18,7 @@
             </p>
           </div>
         </div>
-        <div v-if="item.enterprise?.welfareList && item.enterprise.welfareList.length" class="px-5 py-1 ellipsis" style="border-bottom: 1px solid #EBEBEB;">
+        <div class="px-5 py-1 ellipsis" :style="{'height': '33px', 'border-bottom': item.enterprise.welfareList && item.enterprise.welfareList.length ? '1px solid #EBEBEB;' : 'none'}">
           <span class="welfareTag mr-5" v-for="(k, i) in item.enterprise.welfareList" :key="i">{{ k }}</span>
         </div>
         <!-- 职位列表 -->
@@ -27,12 +27,13 @@
             <div class="job-info" @mouseenter="k.active = true" @mouseleave="k.active = false" @click="handleClickPosition(k)">
               <div class="mb-2 d-flex">
                 <p :class="['name', 'cursor-pointer', {'default-active': k.active }]">{{ k.name }}</p>
-                <span class="salary">{{ k.payFrom }}-{{ k.payTo }}/{{ k.payName }}</span>
+                <span v-if="!k.payFrom && !k.payTo" class="salary">面议</span>
+                <span v-else class="salary">{{ k.payFrom ? k.payFrom + '-' : '' }}{{ k.payTo }}{{ k.payName ? '/' + k.payName : '' }}</span>
               </div>
               <div style="height: 24px; overflow: hidden; color: #808080;">
                 <span v-for="(j, index) in desc" :key="index">
                   <span v-if="k[j.value]" class="mr-1 font-size-13">{{ k[j.value] }}</span>
-                  <span v-if="k[j.value] && index !== desc.length - 1" class="septal-line ml-1"></span>
+                  <span v-if="k[j.value] && index !== desc.length - 1 && k[desc[index + 1].value]" class="septal-line ml-1"></span>
                 </span>
                 <span class="font-size-13 float-right">{{ timesTampChange(k.updateTime, 'Y-M-D') }}</span>
               </div>
@@ -100,7 +101,7 @@ const handleMoreEnterprise = (item) => {
   min-width: calc((100% - 24px) / 3);
   max-width: calc((100% - 24px) / 3);
   margin: 0 12px 12px 0;
-  height: 450px;
+  height: 360px;
   border-radius: 12px;
   padding: 0;
   overflow: hidden;
@@ -122,6 +123,9 @@ const handleMoreEnterprise = (item) => {
   padding: 16px 20px;
   overflow: hidden;
   border-bottom: 1px solid #EBEBEB;
+  &:hover {
+    background-color: #f2f4f7;
+  }
 }
 .welfareTag {
   color: #CEC149;
@@ -162,7 +166,7 @@ ul li {
   padding: 12px 0;
   margin: 0;
   &-hover {
-    background-color: #f8f8f8;
+    background-color: #f2f4f7;
   }
 }
 .salary {

+ 10 - 15
src/components/Position/item.vue

@@ -8,13 +8,14 @@
               <p :class="['name', {'default-active': item.active }]">{{ item.name }}</p>
               <svg-icon v-if="tab === 3 && item.hire" name="pin" size="30"></svg-icon>
             </div>
-            <p class="salary">{{ item.payFrom }}-{{ item.payTo }}/{{ item.payName }}</p>
+            <p v-if="!item.payFrom && !item.payTo" class="salary">面议</p>
+            <p v-else class="salary">{{ item.payFrom ? item.payFrom + '-' : '' }}{{ item.payTo }}{{ item.payName ? '/' + item.payName : '' }}</p>
           </div>
           <div class="d-flex justify-space-between align-center">
             <div>
               <span v-for="(j, i) in desc" :key="i" class="font-size-13" style="color: #808080;">
                 <span v-if="item[j.value]" class="mr-1 d-inline-block">{{ item[j.value] }}</span>
-                <span v-if="i !== desc.length - 1 && item[j.value]" class="septal-line ml-1"></span>
+                <span v-if="i !== desc.length - 1 && item[j.value] && item[desc[i + 1].value]" class="septal-line ml-1"></span>
               </span>
             </div>
           </div>
@@ -32,7 +33,7 @@
               <v-avatar size="35">
                 <v-img :src="item.logoUrl || 'https://minio.citupro.com/dev/menduner/company-avatar.png'" />
               </v-avatar>
-              <span class="names ml-2 font-size-14">{{ item.anotherName }}</span>
+              <span class="names ml-2 font-size-14 ellipsis" style="max-width: 78%;">{{ item.anotherName }}</span>
             </div>
             <p class="float-right color-999 font-size-13">
               <span>{{ item.industryName }}</span>
@@ -43,16 +44,10 @@
         </div>
       </div>
     </div>
-    <!-- 滚动提现数据 -->
-    <div class="pa-3 white-bgc" :style="{height:height}" style="flex: 1; border-radius: 5px; margin: 0 12px 12px 0;">
-      <bountyDisplay></bountyDisplay>
-    </div>
   </div>
 </template>
 
 <script setup>
-import bountyDisplay from '@/views/publicRecruitment/components/bountyDisplay.vue'
-
 defineOptions({ name: 'position-card-item' })
 import { ref, watch } from 'vue'
 import { commissionCalculation } from '@/utils/position'
@@ -97,14 +92,14 @@ const height = ((210 * 2) + 12) + 'px'
 .position-box {
   display: flex;
   flex-wrap: wrap;
-  width: 66.66%;
-  
+  width: 100%;
+  height: 100%;
 }
 .sub-li {
   position: relative;
-  width: calc((100% - 24px) / 2);
-  min-width: calc((100% - 24px) / 2);
-  max-width: calc((100% - 24px) / 2);
+  width: calc((100% - 24px) / 3);
+  min-width: calc((100% - 24px) / 3);
+  max-width: calc((100% - 24px) / 3);
   margin: 0 12px 12px 0;
   border-radius: 12px;
   padding: 0;
@@ -112,7 +107,7 @@ const height = ((210 * 2) + 12) + 'px'
   cursor: pointer;
   transition: all .2s linear;
   background-color: #fff;
-  &:nth-child(2n) {
+  &:nth-child(3n) {
     margin-right: 0;
   }
   &:hover {

+ 4 - 4
src/components/Position/longCompany.vue

@@ -1,13 +1,13 @@
 <template>
   <div>
-    <div class="sub-li mb-3" v-for="item in list" :key="item.id" @mouseenter="item.active = true" @mouseleave="item.active = false">
+    <div class="sub-li mb-3 elevation-2" v-for="item in list" :key="item.id" @mouseenter="item.active = true" @mouseleave="item.active = false">
       <div class="company-info-top">
         <div class="company-info">
           <div class="float-left mr-5">
             <v-img :src="item.logoUrl || 'https://minio.citupro.com/dev/menduner/company-avatar.png'" :alt="item.anotherName" :width="40" style="height: 40px;border-radius: 4px;"/>
           </div>
           <h3 :class="{'default-active': item.active }" class="cursor-pointer" @click="handleClickEnterprise(item, 'briefIntroduction')">{{ item.anotherName }}</h3>
-          <p>{{ item.financingName }}<span class="mx-2">|</span>{{ item.industryName }}<span class="mx-2">|</span>{{ item.scaleName }}</p>
+          <p>{{ item.financingName }}<span v-if="item.financingName && item.industryName" class="mx-2">|</span>{{ item.industryName }}<span v-if="item.scaleName" class="mx-2">|</span>{{ item.scaleName }}</p>
         </div>
         <div v-if="item.active">
           <v-btn class="half-button ml-3" color="primary" size="small" @click.stop="handleCancel(item)">取消收藏</v-btn>
@@ -17,7 +17,7 @@
         <div class="chipBox">
           <div class="d-inline-block" v-for="(val, i) in item.welfareList" :key="i">
             <span>{{ val }}</span>
-            <span class="mx-1" v-if="i !== item.welfareList.length - 1">|</span>
+            <span class="mx-1" v-if="i !== item.welfareList.length - 1 && val && item.welfareList[i + 1]">|</span>
           </div>
         </div>
         <div class="position" @click="handleClickEnterprise(item, 'recruitmentPositions')">
@@ -69,7 +69,7 @@ const handleCancel = async (item) => {
     margin-right: 0;
   }
   &:hover {
-    box-shadow: 0 16px 40px 0 rgba(153, 153, 153, .3);
+    box-shadow: 0px 3px 5px -1px var(--v-shadow-key-umbra-opacity, rgba(0, 0, 0, 0.2)), 0px 5px 8px 0px var(--v-shadow-key-penumbra-opacity, rgba(0, 0, 0, 0.14)), 0px 1px 14px 0px var(--v-shadow-key-ambient-opacity, rgba(0, 0, 0, 0.12)) !important;
   }
 }
 .company-info {

+ 7 - 6
src/components/Position/longStrip.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <div class="position-item mb-3 job-closed" v-for="(val, i) in props.items" :key="i" @mouseenter="val.active = true" @mouseleave="val.active = false">
+    <div class="position-item mb-3 job-closed elevation-2" v-for="(val, i) in props.items" :key="i" @mouseenter="val.active = true" @mouseleave="val.active = false">
       <div class="info-header">
         <div v-if="val.active" class="header-btn">
           <v-btn v-if="props.showCancelBtn" class="half-button ml-3" color="primary" size="small" @click="handleCancel(val)">取消收藏</v-btn>
@@ -20,9 +20,10 @@
             <span v-if="val?.job?.areaName">[{{ val.job.areaName }}]</span>
           </div>
           <div class="job-other">
-            <span class="salary">{{ val.job.payFrom }}-{{ val.job.payTo }}/{{ val.job.payName }}</span>
-            <v-chip class="mx-3" color="primary" label size="small">{{ val.job.expName }}</v-chip>
-            <v-chip color="primary" label size="small">{{ val.job.eduName }}</v-chip>
+            <span v-if="!val.job.payFrom && !val.job.payTo" class="salary">面议</span>
+            <span v-else class="salary">{{ val.job.payFrom ? val.job.payFrom + '-' : ''}}{{ val.job.payTo }}{{ val.job.payName ? '/' + val.job.payName : '' }}</span>
+            <v-chip v-if="val.job?.expName" class="mx-3" color="primary" label size="small">{{ val.job.expName }}</v-chip>
+            <v-chip v-if="val.job?.eduName" color="primary" label size="small">{{ val.job.eduName }}</v-chip>
           </div>
         </div>
         <div class="company-info ml-3" style="flex: 1;">
@@ -34,7 +35,7 @@
             <div class="mt-3 ellipsis color-666 font-size-13" style="max-width: 260px;">
               <span v-for="(k, i) in desc" :key="k">
                 {{ val.enterprise[k] }}
-                <span v-if="i !== desc.length - 1 && val.enterprise[k]" class="septal-line"></span>
+                <span v-if="i !== desc.length - 1 && val.enterprise[k] && val.enterprise[desc[i + 1]]" class="septal-line"></span>
               </span>
             </div>
           </div>
@@ -88,7 +89,7 @@ const handleToPositionDetails = (item) => {
   background-color: #fff;
   border-radius: 12px;
   &:hover {
-    box-shadow: 0 16px 40px 0 rgba(153, 153, 153, .3);
+    box-shadow: 0px 3px 5px -1px var(--v-shadow-key-umbra-opacity, rgba(0, 0, 0, 0.2)), 0px 5px 8px 0px var(--v-shadow-key-penumbra-opacity, rgba(0, 0, 0, 0.14)), 0px 1px 14px 0px var(--v-shadow-key-ambient-opacity, rgba(0, 0, 0, 0.12)) !important;
   }
   .info-header {
     height: 48px;

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

@@ -3,7 +3,8 @@
     <h4 class="mb-3">{{ $t('position.similarPosition') }}</h4>
     <div v-for="(item, index) in props.list" :key="index" class="mb-2 cursor-pointer" @click="handlePosition(item)">
       <p class="recruit-name">{{ item.name }}</p>
-      <span class="recruit-salary">{{ item.payFrom }}-{{ item.payTo }}/{{ item.payName }}</span>
+      <span v-if="!item.payFrom && !item.payTo" class="recruit-salary">面议</span>
+      <span v-else class="recruit-salary">{{ item.payFrom ? item.payFrom + '-' : '' }}{{ item.payTo }}{{ item.payName ? '/' + item.payName :'' }}</span>
       <div :class="['enterprise', {'border-bottom-dashed': index !== list.length - 1}]">
         <v-img class="float-left" :src="item.logoUrl || 'https://minio.citupro.com/dev/menduner/company-avatar.png'" :width="30" :height="30"></v-img>
         <span class="float-left enterprise-name">{{ item.anotherName }}</span>

+ 2 - 1
src/components/PositionLongStrip/item.vue

@@ -13,7 +13,8 @@
             <div class="d-flex">
               <p v-if="item.job.name.indexOf('style')" v-html="item.job.name" :class="['title1', {'default-active': item.positionActive }]"></p>
               <p v-else :class="['title1', {'default-active': item.positionActive }]">{{ item.job.name }}{{ item.job.pos ? ' [' + item.job.pos + '] ' : '' }}</p>
-              <p class="salary ml-1">{{ item.job.payFrom }}-{{ item.job.payTo }}/{{ item.job.payName }}</p>
+              <p v-if="!item.job.payFrom && !item.job.payTo" class="salary ml-1">面议</p>
+              <p v-else class="salary ml-1">{{ item.job.payFrom ? item.job.payFrom + '-' : '' }}{{ item.job.payTo }}{{ item.job.payName ? '/' + item.job.payName : '' }}</p>
               <div v-if="item?.job?.hire">
                 <v-chip v-if="item?.job?.hirePrice && item?.job?.hirePrice > 0" class="ml-3" label color="primary" size="small">赏金:{{ commissionCalculation(item.job.hirePrice, 1) }}元</v-chip>
                 <v-chip v-if="item?.job?.hirePoint && item?.job?.hirePoint > 0" class="ml-3" label color="primary" size="small">积分:{{ commissionCalculation(item.job.hirePoint, 1) }}点</v-chip>

+ 9 - 9
src/components/VerificationCode/index.vue

@@ -5,7 +5,7 @@
         <template v-slot:prepend-inner>
           <span class="d-flex">
             <v-icon icon="mdi-cellphone" size="20"></v-icon>
-            <span class="d-flex" id="menu-activator">
+            <!-- <span class="d-flex" id="menu-activator">
               <span class="phone-number">{{ currentArea }}</span>
               <v-icon size="20">mdi-chevron-down</v-icon>
             </span>
@@ -15,7 +15,7 @@
                   <v-list-item-title>{{ item.label }}</v-list-item-title>
                 </v-list-item>
               </v-list>
-            </v-menu>
+            </v-menu> -->
           </span>
         </template>
       </v-text-field>
@@ -72,13 +72,13 @@ const codeValid = ref([
 ])
 
 // 手机号区域
-const currentArea = ref('0086')
-const items = [
-  { label: '中国大陆-0086', value: '0086' }
-]
-const handleChangeCurrentArea = (e) => {
-  currentArea.value = e.value
-}
+// const currentArea = ref('0086')
+// const items = [
+//   { label: '中国大陆-0086', value: '0086' }
+// ]
+// const handleChangeCurrentArea = (e) => {
+//   currentArea.value = e.value
+// }
 
 // 获取验证码
 const showCode = ref(true)

+ 42 - 36
src/components/headSearch/index.vue

@@ -1,39 +1,42 @@
 <template>
-  <div class="search d-flex align-center">
-    <v-menu v-if="defineProps.text" :close-delay="1" :open-delay="0" v-bind="$attrs">
-      <template v-slot:activator="{ isActive, props }">
-        <v-btn
-          style="height: 100%; font-size: 16px;"
-          variant="text"
-          density="comfortable"
-          :append-icon="isActive ? 'mdi mdi-menu-up' : 'mdi mdi-menu-down'"
-          color="primary"
-          v-bind="props"
-        >
-          {{ defineProps.text }}
-        </v-btn>
-      </template>
-      <jobTypeCard v-if="defineProps.text === '职位类型'" isBuryingPoint class="jobTypeCardBox" @handleJobClick="handleClickJob"></jobTypeCard>
-    </v-menu>
-    <!-- <div style="position: relative;">
-      <div class="jobBox d-flex pl-5" :class="{'drawer': drawer}" @click.stop="drawer = !drawer">
-        <span>{{ text }}</span>
-        <span class="mdi mdi-chevron-down px-2" style="font-size: 18px;"></span>
-      </div>
-      <jobTypeCard class="jobTypeCardBox" v-if="drawer" @click.stop=""></jobTypeCard>
-    </div> -->
-    <v-text-field
-      v-model="value"
-      :placeholder="defineProps.placeholder"
-      color="primary"
-      variant="plain"
-      density="compact"
-      :hide-details="true"
-      class="ml-3 px-2"
-      style="height: 100%; line-height: 100%;"
-      @keyup.enter="handleSearch"
-    ></v-text-field>
-    <div class="searchBtn" @click="handleSearch">搜索</div>
+  <div class="d-flex align-center" :style="{'margin': '0 auto', 'width': tipsText ? '963px': '800px'}">
+    <span v-if="tipsText" class="color-666 mr-3">{{ tipsText }}</span>
+    <div class="search d-flex align-center">
+      <v-menu v-if="defineProps.text" :close-delay="1" :open-delay="0" v-bind="$attrs">
+        <template v-slot:activator="{ isActive, props }">
+          <v-btn
+            style="height: 100%; font-size: 16px;"
+            variant="text"
+            density="comfortable"
+            :append-icon="isActive ? 'mdi mdi-menu-up' : 'mdi mdi-menu-down'"
+            color="primary"
+            v-bind="props"
+          >
+            {{ defineProps.text }}
+          </v-btn>
+        </template>
+        <jobTypeCard v-if="defineProps.text === '职位类型'" isBuryingPoint class="jobTypeCardBox" @handleJobClick="handleClickJob"></jobTypeCard>
+      </v-menu>
+      <!-- <div style="position: relative;">
+        <div class="jobBox d-flex pl-5" :class="{'drawer': drawer}" @click.stop="drawer = !drawer">
+          <span>{{ text }}</span>
+          <span class="mdi mdi-chevron-down px-2" style="font-size: 18px;"></span>
+        </div>
+        <jobTypeCard class="jobTypeCardBox" v-if="drawer" @click.stop=""></jobTypeCard>
+      </div> -->
+      <v-text-field
+        v-model="value"
+        :placeholder="defineProps.placeholder"
+        color="primary"
+        variant="plain"
+        density="compact"
+        :hide-details="true"
+        class="ml-3 px-2"
+        style="height: 100%; line-height: 100%;"
+        @keyup.enter="handleSearch"
+      ></v-text-field>
+      <div class="searchBtn" @click="handleSearch">搜索</div>
+    </div>
   </div>
 </template>
 
@@ -55,6 +58,10 @@ const defineProps = defineProps({
     type: String,
     default: ''
   },
+  tipsText: {
+    type: String,
+    default: ''
+  }
 })
 
 // const value = ref('')
@@ -91,7 +98,6 @@ const handleClickJob = (val) => {
 .search {
   height: 50px;
   width: 800px;
-  margin: 0 auto;
   border: 2px solid var(--v-primary-base);
   border-radius: 5px;
   .jobBox {

+ 0 - 9
src/components/personalRecharge/index.vue

@@ -3,13 +3,6 @@
     <div class="resume-header" v-if="showTitle">
       <div class="resume-title">
         余额充值
-        <span
-          class="ml-2 text-decoration-underline cursor-pointer"
-          style="font-size: 13px;color: #666;"
-          @click="router.push('/recruit/personal/myWallet/myBalance')"
-        >
-          (充值记录)
-        </span>
       </div>
     </div>
     <div class="d-flex align-center mb-10 mt-5">
@@ -45,7 +38,6 @@
             <span v-if="!item.custom">¥{{ FenYuanTransform(item.payPrice) }}</span>
             <span v-else>{{ item.tip }}</span>
           </div>
-          <v-btn v-if="!item.custom" class="mt-5" size="small" color="error" variant="outlined" rounded >立即充值</v-btn>
           <div class="vip">
             <svg-icon v-if="current === (index+1)" name="diamond-active" size="50"></svg-icon>
             <svg-icon v-else name="diamond" size="50"></svg-icon>
@@ -77,7 +69,6 @@ defineProps({
 })
 
 import { useRoute } from 'vue-router'; const route = useRoute()
-import { useRouter } from 'vue-router'; const router = useRouter()
 const fullShow = ref(route.path === '/personalRecharge' ? true : false)
 
 const current = ref(0)

+ 28 - 26
src/config/axios/service.js

@@ -20,6 +20,8 @@ import { useI18n } from '@/hooks/web/useI18n'
 
 const tenantEnable = import.meta.env.VITE_APP_TENANT_ENABLE
 const { result_code, base_url, request_timeout } = config
+// const entUrlStr = '/recruit' // 包含这个就是企业接口,特殊的在api function中设置tokenIndex(优先)
+// let isEnterprise = config.url.includes(entUrlStr)
 
 // 需要忽略的提示。忽略后,自动 Promise.reject('error')
 const ignoreMsgs = [
@@ -58,6 +60,8 @@ service.interceptors.request.use(
     config.headers['Accept-Language'] = getCurrentLocaleLang() ?? 'zh_CN'
     // 是否需要设置 token
     let isToken = (config.headers || {}).isToken === false
+    // token类型
+    const tokenIndex = config.tokenIndex ? config.tokenIndex : getIsEnterprise() ? 1 : 2
     whiteList.some((v) => {
       if (config.url) {
         config.url.indexOf(v) > -1
@@ -65,8 +69,8 @@ service.interceptors.request.use(
       }
     })
     
-    if (getToken(config.tokenIndex) && !isToken) {
-      (config).headers.Authorization = 'Bearer ' + getToken(config.tokenIndex) // 让每个请求携带自定义token
+    if (getToken(tokenIndex) && !isToken) {
+      (config).headers.Authorization = 'Bearer ' + getToken(tokenIndex) // 让每个请求携带自定义token
     }
     // 设置租户
     if (tenantEnable && tenantEnable === 'true') {
@@ -90,12 +94,15 @@ service.interceptors.request.use(
         config.url = config.url + '?' + paramsStr
       }
     }
-    // 截取request url
-    const url = getSuffixAfterPrefix(config.url)
-    if (!url || !clickTrigger.length) return config
-    const _obj = clickTrigger.find(e => e.url === url)
-    if (_obj) {
-      getIntegral(_obj.url, userStore)
+    
+    if (isToken) {
+      // 截取request url
+      const url = getSuffixAfterPrefix(config.url)
+      if (!url || !clickTrigger.length) return config
+      const _obj = clickTrigger.find(e => e.url === url)
+      if (_obj) {
+        getIntegral(_obj.url, userStore)
+      }
     }
     return config
   },
@@ -143,6 +150,9 @@ service.interceptors.response.use(
       return Promise.reject(msg)
     } else if (code === 401) {
       // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了
+      // token类型
+      const tokenIndex = config.tokenIndex ? config.tokenIndex : getIsEnterprise() ? 1 : 2
+
       if (!isRefreshToken) {
         isRefreshToken = true
         // 1. 如果获取不到刷新令牌,则只能执行登出操作
@@ -152,11 +162,11 @@ service.interceptors.response.use(
         // 2. 进行刷新访问令牌
         try {
           // 2.1 刷新成功,则回放队列的请求 + 当前请求
-          const data = getIsEnterprise() ? await enterpriseRefreshToken(getRefreshToken()) : await userRefreshToken(getRefreshToken())
+          const data = tokenIndex === 1 ? await enterpriseRefreshToken(getRefreshToken()) : await userRefreshToken(getRefreshToken())
           setToken(data.accessToken)
           setRefreshToken(data.refreshToken)
 
-          config.headers.Authorization = 'Bearer ' + getToken(config.tokenIndex)
+          config.headers.Authorization = 'Bearer ' + getToken(tokenIndex)
           requestList.forEach((cb) => {
             cb()
           })
@@ -178,7 +188,7 @@ service.interceptors.response.use(
         // 添加到队列,等待刷新获取到新的令牌
         return new Promise((resolve) => {
           requestList.push(() => {
-            config.headers.Authorization = 'Bearer ' + getToken(config.tokenIndex) // 让每个请求携带自定义token 请根据实际情况自行修改
+            config.headers.Authorization = 'Bearer ' + getToken(tokenIndex) // 让每个请求携带自定义token 请根据实际情况自行修改
             resolve(service(config))
           })
         })
@@ -201,21 +211,13 @@ service.interceptors.response.use(
     }
     
     // 请求成功后触发获取积分
-    // if (!requestCompletionTrigger.length) {
-    //   return data
-    // }
-    // const _list = requestCompletionTrigger.filter(_e => {
-    //   return response.config.url.includes(_e.url)
-    // })
-    // if (_list.length) {
-    //   getIntegral(_list.pop().url, userStore)
-    // }
-
-    const url = getSuffixAfterPrefix(response.config.url)
-    if (!url || !requestCompletionTrigger.length) return data
-    const _obj = requestCompletionTrigger.find(e => e.url === url)
-    if (_obj) {
-      getIntegral(_obj.url, userStore)
+    if (response.config.headers?.Authorization) {
+      const url = getSuffixAfterPrefix(response.config.url)
+      if (!url || !requestCompletionTrigger.length) return data
+      const _obj = requestCompletionTrigger.find(e => e.url === url)
+      if (_obj) {
+        getIntegral(_obj.url, userStore)
+      }
     }
     return data
   },

+ 6 - 6
src/layout/company/navBar.vue

@@ -17,7 +17,7 @@
             <v-img @click="enterpriseClick(2)" rounded width="40" height="40" :src="baseInfo?.logoUrl || 'https://minio.citupro.com/dev/menduner/company-avatar.png'" ></v-img>
             <span @click="enterpriseClick(1)" class="ml-3 commonHover">{{ baseInfo?.enterpriseAnotherName || baseInfo?.enterpriseName || '--' }}</span>
           </div>
-          <div class="ml-3 cursor-pointer border-left border-right px-3 commonHover" @click="handleLogout">我要求职</div>
+          <div class="ml-3 cursor-pointer border-left border-right px-3 commonHover" @click="router.push('/recruitHome')">我要求职</div>
           <div class="cursor-pointer border-right px-3 commonHover" @click="router.push('/recruit/enterprise/hirePosition')">全员猎聘</div>
           <div class="d-flex align-center px-3 border-right">
             <div>{{ $t('enterprise.account.remainingPoints') }}:{{ enterpriseUserAccount?.balance || 0 }}点</div>
@@ -105,7 +105,7 @@ defineProps({
 const showBall = ref(false)
 
 onMounted(() => {
-  if (getToken()) {
+  if (getToken(1)) {
     showBall.value = true
   }
 })
@@ -124,14 +124,14 @@ const enterpriseClick = (tabKey = 1) => {
 // 退出登录
 const handleLogout = async () => {
   await userStore.userLogout(2)
-  router.push({ path: '/login' })
+  router.push({ path: '/recruitHome' })
 }
 const enterpriseList = ref([])
 
 const menuList = ref([
-  { title: t('enterprise.account.myAccount'), icon: 'mdi-account', change: () => router.push({ path: '/recruit/enterprise/memberCenter/myAccount' }) },
-  { title: t('vipPackage.purchasePackage'), icon: 'mdi-gift-outline', change: () => window.open('/recruit/enterprise/purchasePackage') },
-  { title: t('enterprise.personalInformationSettings'), icon: 'mdi-account-cog', change: () => router.push({ path: '/recruit/enterprise/informationSettings' }) },
+  // { title: t('enterprise.account.myAccount'), icon: 'mdi-account', change: () => router.push({ path: '/recruit/enterprise/memberCenter/myAccount' }) },
+  // { title: t('vipPackage.purchasePackage'), icon: 'mdi-gift-outline', change: () => window.open('/recruit/enterprise/purchasePackage') },
+  // { title: t('enterprise.personalInformationSettings'), icon: 'mdi-account-cog', change: () => router.push({ path: '/recruit/enterprise/informationSettings' }) },
   // { title: t('setting.switchToOtherCompany'), icon: 'mdi-home-switch', hidden: enterpriseList.value?.length < 2, change: () => handleSwitchToAnotherEnterprise },
   // { title: t('enterprise.registeringNewEnterprise'), icon: 'mdi-home-plus-outline', change: () => handleRegisteringNewEnterprise },
   // { title: t('setting.switchToJobSeeker'), icon: 'mdi-swap-horizontal', change: handleLogout },

+ 14 - 13
src/layout/personal/navBar.vue

@@ -24,7 +24,7 @@
                 <defineListPage v-bind="$attrs" :title="val.title" :list="val.children" @emitClick="handleClick" :closeOnContentClick="true"></defineListPage>
               </template>
               <template v-else>
-                <span class="cursor-pointer menuList-first-title" @click="handleClick(val, true)">{{ val.title }}</span>
+                <span class="cursor-pointer menuList-first-title" :class="{'active-route' : route.path === val.path}" @click="handleClick(val, true)">{{ val.title }}</span>
               </template>
             </div>
           </div>
@@ -39,9 +39,9 @@
           <div class="d-flex align-center color-333" v-if="getToken()">
             <span class="cursor-pointer mr-5 commonHover" @click="handleSwitch">我要招聘</span>
             <span class="cursor-pointer commonHover" :class="{'active-route': routeActive === 6}" @click="router.push({ path: paths[6] })">
-              {{t('enterprise.account.accountBalances')}}:{{ userAccount?.balance && userAccount?.balance > 0 ? (userAccount?.balance / 100.0).toFixed(2) : 0 }}
+              现金:{{ userAccount?.balance && userAccount?.balance > 0 ? (userAccount?.balance / 100.0).toFixed(2) : 0 }}
             </span>
-            <span class="mr-3 ml-3 cursor-pointer commonHover" :class="{'active-route': routeActive === 7}" @click="router.push({ path: paths[7] })">{{t('resume.goldCoins')}}:{{ userAccount?.point || 0 }}</span>
+            <span class="mr-3 ml-3 cursor-pointer commonHover" :class="{'active-route': routeActive === 7}" @click="router.push({ path: paths[7] })">积分:{{ userAccount?.point || 0 }}</span>
 
             <v-menu open-on-hover>
               <template v-slot:activator="{ props }">
@@ -170,10 +170,10 @@ const navList = [
   },
   { title: '甄选', path: '/mall' },
   { title: '猎寻', path: '/headhunting' },
-  { title: '数据' },
   { title: '火苗儿' },
   { title: '联合会' },
-  { title: '了解门墩儿' },
+  { title: '数据' },
+  { title: '了解门墩儿' }
 ]
 
 const handleClick = (e, status) => {
@@ -199,7 +199,7 @@ const handleSwitch = async () => {
     radios.value = data[0].enterpriseId
     show.value = true
   } else {
-    // router.push('recruit/enterprise/register')
+    // router.push('recruit/enterpriseRegister')
     newRegistering() // 既没有存在的企业 又没有申请数据 》 进入注册页面
   }
 }
@@ -232,7 +232,7 @@ const getApplyInfo = async () => {
 const loading1 = ref(false)
 const newRegistering = () => {
   loading1.value = true
-  const path = btnType.value ? '/recruit/enterprise/register' : '/recruit/enterprise/register/inReview'
+  const path = btnType.value ? '/recruit/entRegister' : '/recruit/entRegister/inReview'
   router.replace({ path })
 }
 
@@ -243,17 +243,18 @@ const handleToPersonalCenter = () => {
 // 退出登录
 const handleLogout = async () => {
   await userStore.userLogout(1)
-  router.push({ path: '/login' })
+  if (route.path === '/recruitHome') location.reload()
+  else router.push({ path: '/recruitHome' })
 }
 
 const items = ref([
-  { title: t('vipPackage.purchasePackage'), icon: 'mdi-gift-outline', change: () => router.push({ path: '/recruit/personal/purchasePackage' }) },
-  { title: t('resume.onlineResume'), icon: 'mdi-list-box-outline', change: () => router.push({ path: '/recruit/personal/resume' }) },
-  { title: t('setting.accountSettings'), icon: 'mdi-cog-outline', change: () => router.push({ path: '/recruit/personal/accountSettings/accountBinding' }) },
+  // { title: t('vipPackage.purchasePackage'), icon: 'mdi-gift-outline', change: () => router.push({ path: '/recruit/personal/purchasePackage' }) },
+  // { title: t('resume.onlineResume'), icon: 'mdi-list-box-outline', change: () => router.push({ path: '/recruit/personal/resume' }) },
+  // { title: t('setting.accountSettings'), icon: 'mdi-cog-outline', change: () => router.push({ path: '/recruit/personal/accountSettings/accountBinding' }) },
   // { title: t('setting.switchToRecruit'), icon: 'mdi-swap-horizontal', change: handleSwitch },
   { title: t('publicRecruitment.bountyRewards'), icon: 'mdi-google-circles-extended', change: () => router.push({ path: '/publicRecruitment' }) },
-  { title: t('points.wallet'), icon: ' mdi-database-outline', change: () => router.push({ path: '/recruit/personal/myWallet' }) },
-  { title: t('taskCenter.taskCenter'), icon: 'mdi-calendar-check-outline', change: () => router.push({ path: '/recruit/personal/TaskCenter' }) },
+  // { title: t('points.wallet'), icon: ' mdi-database-outline', change: () => router.push({ path: '/recruit/personal/myWallet' }) },
+  // { title: t('taskCenter.taskCenter'), icon: 'mdi-calendar-check-outline', change: () => router.push({ path: '/recruit/personal/TaskCenter' }) },
   { title: t('setting.logOut'), icon: 'mdi-logout', change: handleLogout }
 ])
 

+ 1 - 1
src/locales/en.js

@@ -176,7 +176,7 @@ export default {
     companyInfo: 'Company Information',
     moreBtn: 'View More Enterprises',
     joiningEnterprise: 'Joining Enterprise',
-    registeringNewEnterprise: 'Registering A New Enterprise',
+    registeringNewEnterprise: 'Registering A Enterprise',
     infoSetting: { // 企业信息设置
       basicInfo: 'Basic Information',
       enterpriseLogo: 'Enterprise LOGO',

+ 1 - 1
src/locales/zh-CN.js

@@ -176,7 +176,7 @@ export default {
     companyInfo: '公司信息',
     moreBtn: '查看更多企业',
     joiningEnterprise: '加入已有企业',
-    registeringNewEnterprise: '注册企业',
+    registeringNewEnterprise: '注册企业',
     infoSetting: { // 企业信息设置
       basicInfo: '基本信息',
       enterpriseLogo: '企业LOGO',

+ 3 - 2
src/permission.js

@@ -1,7 +1,7 @@
 import router from './router'
 import { useNProgress } from '@/hooks/web/useNProgress'
 import { useTitle } from '@/hooks/web/useTitle'
-import { getToken } from '@/utils/auth'
+import { getToken, getIsEnterprise } from '@/utils/auth'
 import { useDictStore } from '@/store/dict'
 import { useUserStore } from '@/store/user'
 import Confirm from '@/plugins/confirm'
@@ -14,12 +14,13 @@ const { start, done } = useNProgress()
 // 路由守卫
 router.beforeEach(async (to, from, next) => {
   localStorage.setItem('routerTest', to.path) // 本地环境保存代码热更新会导致路径缺失问题
+  const tokenIndex = getIsEnterprise() ? 1: 2
   start()
   // loadStart()
   if (to.path === '/enterpriseVerification') {
     useUserStore().changeRole()
     next()
-  } else if (getToken()) {
+  } else if (getToken(tokenIndex)) {
     // 企业信息完成度提示
     if (localStorage.getItem('checkEnterpriseBaseInfoFalseHref')) {
       const href = localStorage.getItem('checkEnterpriseBaseInfoFalseHref')

+ 7 - 19
src/router/modules/common.js

@@ -18,18 +18,6 @@ const common = [
       }
     ]
   },
-  {
-    path: '/mall',
-    children: [
-      {
-        path: '/mall',
-        component: () => import('@/views/mall/index'),
-        meta: {
-          title: '臻选商城'
-        }
-      }
-    ]
-  },
   {
     path: '/shareJob',
     name: 'shareJob',
@@ -55,27 +43,27 @@ const common = [
     component: () => import('@/views/recruit/enterprise/systemManagement/groupAccount/inviteConfirmEnt.vue')
   },
   {
-    path: '/recruit/enterprise/register',
+    path: '/recruit/entRegister',
     // component: Layout,
     name: 'enterpriseRegister',
     children: [
       {
-        path: '/recruit/enterprise/register',
-        component: () => import('@/views/recruit/enterprise/register/register.vue'),
+        path: '/recruit/entRegister',
+        component: () => import('@/views/recruit/entRegister/register.vue'),
         meta: {
           title: '注册新企业'
         },
       },
       {
-        path: '/recruit/enterprise/register/joiningEnterprise',
-        component: () => import('@/views/recruit/enterprise/register/joiningEnterprise'),
+        path: '/recruit/entRegister/joiningEnterprise',
+        component: () => import('@/views/recruit/entRegister/joiningEnterprise'),
         meta: {
           title: '加入企业'
         }
       },
       {
-        path: '/recruit/enterprise/register/inReview',
-        component: () => import('@/views/recruit/enterprise/register/inReview.vue'),
+        path: '/recruit/entRegister/inReview',
+        component: () => import('@/views/recruit/entRegister/inReview.vue'),
         meta: {
           title: '申请信息'
         }

+ 7 - 0
src/router/modules/components/headhunting.js

@@ -12,6 +12,13 @@ const headhunting = [
     meta: {
       title: '我们的服务'
     }
+  },
+  {
+    path: '/headhunting/service/details',
+    component: () => import('@/views/headhunting/details.vue'),
+    meta: {
+      title: '门墩儿猎寻服务'
+    }
   }
 ]
 

+ 94 - 0
src/router/modules/components/recruit/personCenter.js

@@ -0,0 +1,94 @@
+// 个人中心路由
+import Layout from '@/layout'
+
+const personCenter = [
+  {
+    path: '/recruit/personal/personalCenter',
+    component: Layout,
+    name: 'personalCenter',
+    meta: {
+      title: '个人中心'
+    },
+    children: [
+      {
+        path: '/recruit/personal/personalCenter',
+        redirect: '/recruit/personal/personalCenter/jobFeedback',
+        component: () => import('@/views/recruit/personal/PersonalCenter/index'),
+        children: [
+          {
+            path: '/recruit/personal/personalCenter/jobFeedback',
+            name: 'Job Feedback',
+            component: () => import('@/views/recruit/personal/PersonalCenter/jobFeedback/index.vue'),
+            meta: {
+              title: '求职反馈',
+              enName: 'Job Feedback',
+              icon: 'mdi-account-sync-outline'
+            }
+          },
+          {
+            path: '/recruit/personal/personalCenter/wallet',
+            name: 'My Wallet',
+            component: () => import('@/views/recruit/personal/PersonalCenter/wallet/index.vue'),
+            meta: {
+              title: '我的钱包',
+              enName: 'My Wallet',
+              icon: 'mdi-wallet-outline'
+            }
+          },
+          {
+            path: '/recruit/personal/personalCenter/taskCenter',
+            name: 'Task Center',
+            component: () => import('@/views/recruit/personal/PersonalCenter/taskCenter/index.vue'),
+            meta: {
+              title: '任务中心',
+              enName: 'Task Center',
+              icon: 'mdi-movie-star-outline'
+            }
+          },
+          {
+            path: '/recruit/personal/personalCenter/accountSettings',
+            redirect: '/recruit/personal/personalCenter/accountSettings/editPassword',
+            name: 'Account Settings',
+            meta: {
+              title: '账号设置',
+              enName: 'Account Settings',
+              icon: 'mdi-cog-outline'
+            },
+            children: [
+              {
+                path: '/recruit/personal/personalCenter/accountSettings/editPassword',
+                component: () => import('@/views/recruit/personal/PersonalCenter/accountSettings/editPassword'),
+                meta: {
+                  title: '修改密码'
+                }
+              },
+              // {
+              //   path: '/recruit/personal/personalCenter/accountSettings/accountBinding',
+              //   component: () => import('@/views/recruit/personal/PersonalCenter/accountSettings/accountBinding'),
+              //   meta: {
+              //     title: '账号绑定'
+              //   }
+              // },
+              // {
+              //   path: '/recruit/personal/personalCenter/accountSettings/realAuthentication',
+              //   component: () => import('@/views/recruit/personal/PersonalCenter/accountSettings/realAuthentication'),
+              //   meta: {
+              //     title: '实名认证'
+              //   }
+              // },
+              // {
+              //   path: '/recruit/personal/personalCenter/accountSettings/privacySettings',
+              //   component: () => import('@/views/recruit/personal/PersonalCenter/accountSettings/privacySettings'),
+              //   meta: {
+              //     title: '隐私设置'
+              //   }
+              // }
+            ]
+          }
+        ]
+      }
+    ]
+  },
+]
+
+export default personCenter

+ 24 - 65
src/router/modules/components/recruit/personal.js

@@ -1,51 +1,9 @@
 // 个人路由信息
 
 import Layout from '@/layout'
+import personCenter from './personCenter'
+
 const personal = [
-  {
-    path: '/recruit/personal/accountSettings',
-    component: Layout,
-    name: 'personalAccount',
-    children: [
-      {
-        path: '/recruit/personal/accountSettings',
-        component: () => import('@/views/recruit/personal/accountSettings/index'),
-        meta: {
-          title: '账号设置'
-        },
-        children: [
-          {
-            path: '/recruit/personal/accountSettings/editPassword',
-            component: () => import('@/views/recruit/personal/accountSettings/dynamic/editPassword'),
-            meta: {
-              title: '修改密码'
-            }
-          },
-          {
-            path: '/recruit/personal/accountSettings/accountBinding',
-            component: () => import('@/views/recruit/personal/accountSettings/dynamic/accountBinding'),
-            meta: {
-              title: '账号加入'
-            }
-          },
-          {
-            path: '/recruit/personal/accountSettings/realAuthentication',
-            component: () => import('@/views/recruit/personal/accountSettings/dynamic/realAuthentication'),
-            meta: {
-              title: '实名认证'
-            }
-          },
-          {
-            path: '/recruit/personal/accountSettings/privacySettings',
-            component: () => import('@/views/recruit/personal/accountSettings/dynamic/privacySettings'),
-            meta: {
-              title: '隐私设置'
-            }
-          }
-        ]
-      }
-    ]
-  },
   {
     path: '/recruit/personal/resume',
     component: Layout,
@@ -60,27 +18,27 @@ const personal = [
       }
     ]
   },
-  {
-    path: '/recruit/personal/personalCenter',
-    component: Layout,
-    name: 'personalCenter',
-    meta: {
-      title: '个人中心'
-    },
-    children: [
-      {
-        path: '/recruit/personal/personalCenter',
-        component: () => import('@/views/recruit/personal/PersonalCenter/index')
-      },
-      {
-        path: '/recruit/personal/tradeOrder',
-        meta: {
-          title: '我的订单'
-        },
-        component: () => import('@/views/recruit/personal/PersonalCenter/components/order.vue')
-      }
-    ]
-  },
+  // {
+  //   path: '/recruit/personal/personalCenter',
+  //   component: Layout,
+  //   name: 'personalCenter',
+  //   meta: {
+  //     title: '个人中心'
+  //   },
+  //   children: [
+  //     {
+  //       path: '/recruit/personal/personalCenter',
+  //       component: () => import('@/views/recruit/personal/PersonalCenter/index')
+  //     },
+  //     {
+  //       path: '/recruit/personal/tradeOrder',
+  //       meta: {
+  //         title: '我的订单'
+  //       },
+  //       component: () => import('@/views/recruit/personal/PersonalCenter/components/order.vue')
+  //     }
+  //   ]
+  // },
   {
     path: '/publicRecruitment',
     component: Layout,
@@ -204,5 +162,6 @@ const personal = [
       }
     ]
   },
+  ...personCenter
 ]
 export default personal

+ 14 - 0
src/router/modules/recruit.js

@@ -31,6 +31,20 @@ const recruit = [
       }
     ]
   },
+  {
+    path: '/mall',
+    component: Layout,
+    children: [
+      {
+        path: '/mall',
+        component: () => import('@/views/mall/index'),
+        name: 'mall',
+        meta: {
+          title: '甄选商城'
+        }
+      }
+    ]
+  },
   {
     path: '/recruit/personal/position',
     component: Layout,

+ 36 - 0
src/router/modules/remaining.js

@@ -14,6 +14,42 @@ const remainingRouter = [
       title: '登录/注册'
     }
   },
+  {
+    path: '/register/selected',
+    component: () => import('@/views/register/select.vue'),
+    name: 'registerSelect',
+    meta: {
+      hidden: true,
+      title: '注册'
+    }
+  },
+  {
+    path: '/register/person',
+    component: () => import('@/views/register/person.vue'),
+    name: 'personRegister',
+    meta: {
+      hidden: true,
+      title: '个人用户注册'
+    }
+  },
+  {
+    path: '/register/company',
+    component: () => import('@/views/register/company.vue'),
+    name: 'companyRegister',
+    meta: {
+      hidden: true,
+      title: '企业用户注册'
+    }
+  },
+  {
+    path: '/forgotPassword',
+    component: () => import('@/views/login/forgotPassword.vue'),
+    name: 'forgotPassword',
+    meta: {
+      hidden: true,
+      title: '找回密码'
+    }
+  },
   {
     path: '/userAgreement',
     component: Layout,

+ 26 - 23
src/store/user.js

@@ -3,9 +3,7 @@ import { setToken, removeToken, setRefreshToken, getToken } from '@/utils/auth'
 import {
   smsLogin,
   passwordLogin, 
-  getBaseInfo, 
-  passwordLoginOfEnterprise, 
-  smsLoginOfEnterprise, 
+  getBaseInfo,
   switchLoginOfEnterprise, 
   getEnterprisingUserInfo, 
   logoutToken, 
@@ -28,7 +26,6 @@ import { getBaseInfoDictOfName } from '@/utils/getText'
 export const useUserStore = defineStore('user',
   {
     state: () => ({
-      loginType: localStorage.getItem('loginType'), 
       accountInfo: localStorage.getItem('accountInfo') ? JSON.parse(localStorage.getItem('accountInfo')) : {}, // 登录返回的信息
       userInfo: localStorage.getItem('userInfo') ? JSON.parse(localStorage.getItem('userInfo')) : {}, // 当前登录账号信息
       baseInfo: localStorage.getItem('baseInfo') ? JSON.parse(localStorage.getItem('baseInfo')) : {}, // 人才信息
@@ -40,8 +37,7 @@ export const useUserStore = defineStore('user',
       // 短信登录
       handleSmsLogin (data) {
         return new Promise((resolve, reject) => {
-          const loginApi = data.loginType === 330 ? smsLoginOfEnterprise : smsLogin
-          loginApi(data).then(async res => {
+          smsLogin(data).then(async res => {
             setToken(res.accessToken)
             setRefreshToken(res.refreshToken)
             this.accountInfo = res
@@ -57,16 +53,19 @@ export const useUserStore = defineStore('user',
       // 密码登录
       async handlePasswordLogin(data) {
         return new Promise((resolve, reject) => {
-          const loginApi = data.loginType === 330 ? passwordLoginOfEnterprise : passwordLogin
           data.account = data.phone
-          loginApi(data).then(res => {
-            setToken(res.accessToken)
-            setRefreshToken(res.refreshToken)
-            this.accountInfo = res
-            localStorage.setItem('accountInfo', JSON.stringify(res))
-            localStorage.setItem('expiresTime', res.expiresTime) // token过期时间
-            this.getUserInfos()
-            this.getUserBaseInfos()
+          passwordLogin(data).then(res => {
+            if (data.isEnterprise) { // 企业邮箱登录
+              this.changeRole('emailLogin', res)
+            } else {
+              setToken(res.accessToken)
+              setRefreshToken(res.refreshToken)
+              this.accountInfo = res
+              localStorage.setItem('accountInfo', JSON.stringify(res))
+              localStorage.setItem('expiresTime', res.expiresTime) // token过期时间
+              this.getUserInfos()
+              this.getUserBaseInfos()
+            }
             resolve()
           }).catch(err => { reject(err) })
         })
@@ -115,7 +114,7 @@ export const useUserStore = defineStore('user',
         // type: 1求职端 2招聘端
         if (type === 1) {
           await logout()
-        } else await logoutToken(getToken())
+        } else await logoutToken(getToken(1))
         removeToken()
         this.userInfo = {}
         this.baseInfo = {}
@@ -123,13 +122,17 @@ export const useUserStore = defineStore('user',
         localStorage.clear()
       },
       // 切换为招聘者
-      async changeRole () {
-        // 先退出个人登录
-        // await logout()
-        const enterpriseId = localStorage.getItem('enterpriseId') || ''
-        if (!enterpriseId) return Snackbar.error('切换失败,请重新登录!')
-
-        const data = await switchLoginOfEnterprise({ enterpriseId })
+      async changeRole (type, res) {
+        let data
+        if (type === 'emailLogin') {
+          data = res
+        } else {
+          // 先退出个人登录
+          // await logout()
+          const enterpriseId = localStorage.getItem('enterpriseId') || ''
+          if (!enterpriseId) return Snackbar.error('切换失败,请重新登录!')
+          data = await switchLoginOfEnterprise({ enterpriseId })
+        }
         setToken(data.accessToken, 1) // 个人切换企业->存放企业token
         setRefreshToken(data.refreshToken, 1) // 个人切换企业->存放企业refreshToken
         localStorage.setItem('accountInfo', JSON.stringify(data))

+ 10 - 12
src/utils/auth.js

@@ -12,23 +12,21 @@ const isEnterprise = () => {
   const testUsePath = localStorage.getItem('routerTest')
   if (currentRoute?.path === '/' && (testUsePath?.includes(substr) || testUsePath?.includes('/enterpriseVerification'))) bool = true
   // console.log('currentRoute', currentRoute.path)
-  // console.log('isEnterprise-bool', bool)
+  // console.log('isEnterpriseBool', bool)
   return bool
 }
 
-export const getIsEnterprise = () => { return isEnterprise() }
+export const getIsEnterprise = () => { return isEnterprise() } // 是否是企业路由
 
 // 获取token
-export const getToken = (index) => { // index=1: 使用招聘token; index=2: 使用求职token
+export const getToken = (index = 2) => { // index=1: 使用招聘token; index=2: 使用求职token
   const arr = ['ENT_ACCESS_TOKEN', 'PER_ACCESS_TOKEN']
-  let type = index ? arr[index-1] : isEnterprise() ? arr[0] : arr[1]
-  //
-  return localStorage.getItem(type)
+  return localStorage.getItem(arr[index-1])
 }
 
 // 设置token
-export const setToken = (token, switchToEnt = 0) => { // 个人切换企业switchToEnt = 1
-  return localStorage.setItem((switchToEnt || isEnterprise() ? 'ENT_ACCESS_TOKEN' : 'PER_ACCESS_TOKEN'), token)
+export const setToken = (token, index = 2) => { // index=1: 招聘token; index=2: 求职token
+  return localStorage.setItem((index === 1 ? 'ENT_ACCESS_TOKEN' : 'PER_ACCESS_TOKEN'), token)
 }
 
 // 清除token
@@ -39,13 +37,13 @@ export const removeToken = () => {
   localStorage.removeItem('PER_REFRESH_TOKEN')
 }
 // 获取刷新token
-export const getRefreshToken = () => {
-  return localStorage.getItem(isEnterprise() ? 'ENT_REFRESH_TOKEN' : 'PER_REFRESH_TOKEN')
+export const getRefreshToken = (index) => {
+  return localStorage.getItem(index === 1 ? 'ENT_REFRESH_TOKEN' : 'PER_REFRESH_TOKEN')
 }
 
 // 设置刷新token
-export const setRefreshToken = (refreshToken, switchToEnt = 0) => { // 个人切换企业switchToEnt = 1
-  return localStorage.setItem((switchToEnt || isEnterprise() ? 'ENT_REFRESH_TOKEN' : 'PER_REFRESH_TOKEN'), refreshToken)
+export const setRefreshToken = (refreshToken, index = 2) => { // index=1: 招聘token; index=2: 求职token
+  return localStorage.setItem((index === 1 ? 'ENT_REFRESH_TOKEN' : 'PER_REFRESH_TOKEN'), refreshToken)
 }
 
 // 租户ID

+ 2 - 2
src/utils/eventList.js

@@ -13,13 +13,13 @@ const getRewardEventList = async () => {
 export const updateEventList = (type) => {
   const store = useUserStore()
   const timer = ref(null)
-  if (getToken()) {
+  if (getToken(1) || getToken()) {
     getRewardEventList()
     if (timer.value) clearInterval(timer.value)
       
     timer.value = setInterval(async () => {
       // 没有token时销毁定时器
-      if (!getToken()) {
+      if (!getToken(1) || !getToken()) {
         clearInterval(timer.value)
         timer.value = null
         return

+ 68 - 0
src/utils/headhuntingData.js

@@ -0,0 +1,68 @@
+// 五大服务模块钻取
+export const serviceData = [
+  {
+    title: '高级管理精英甄选',
+    startDesc: '我们深知企业在转型升级过程中,高级管理人才的重要性。因此,门墩儿猎头凭借丰富的行业经验、专业的人才筛选能力和严谨的服务态度,为企业量身定制高级管理人才解决方案。我们的高级管理精英甄选业务涵盖各行各业,包括但不限于金融、互联网、房地产、制造业等领域。我们通过深入了解企业发展战略、企业文化及岗位需求,运用先进的猎头技术和丰富的人才资源库,为企业精准挖掘、评估和推荐具备以下特质的高级管理人才:',
+    endDesc: '门墩儿猎头始终秉承“客户至上、诚信为本”的服务宗旨,以专业、高效、贴心的服务,为企业输送了一批又一批高级管理精英,助力企业实现战略目标。我们相信,在门墩儿猎头的助力下,企业将不断迈向新的征程。',
+    children: [
+      '1. 高度的职业素养:具备良好的职业道德,对企业忠诚,能够带领团队实现企业目标。',
+      '2. 卓越的领导能力:具备较强的团队建设和管理能力,能够激发团队成员潜能,推动企业持续发展。',
+      '3. 丰富的实战经验:具备丰富的行业经验和成功案例,能够为企业带来实质性的变革和创新。',
+      '4. 强大的抗压能力:在面对复杂多变的市场环境时,能够保持冷静,为企业稳定发展保驾护航。'
+    ]
+  },
+  {
+    title: '人才发展储备策略',
+    startDesc: '我们深知企业在转型升级过程中,高级管理人才的重要性。因此,门墩儿猎头凭借丰富的行业经验、专业的人才筛选能力和严谨的服务态度,为企业量身定制高级管理人才解决方案。我们的高级管理精英甄选业务涵盖各行各业,包括但不限于金融、互联网、房地产、制造业等领域。我们通过深入了解企业发展战略、企业文化及岗位需求,运用先进的猎头技术和丰富的人才资源库,为企业精准挖掘、评估和推荐具备以下特质的高级管理人才:',
+    startTitle: '门墩儿猎头的人才发展储备策略主要包括以下几个方面:',
+    endDesc: '通过门墩儿猎头的人才发展储备策略,企业不仅能够有效应对人才流失的风险,还能在激烈的市场竞争中保持核心竞争力,实现可持续发展。我们致力于成为企业信赖的人才战略合作伙伴,为企业的发展壮大提供源源不断的动力。',
+    children: [
+      '1. 人才需求分析:我们通过对企业战略目标、业务发展和组织结构的深入分析,精准把握企业未来的人才需求,为企业制定合理的人才储备规划。',
+      '2. 人才选拔与评估:我们运用科学的选拔方法和专业的评估体系,从海量候选人中筛选出具备潜力和成长空间的人才,为企业储备一批高素质的候选人。',
+      '3. 人才培养与激励:门墩儿猎头协助企业制定个性化的培养计划,通过内部培训、外部学习、导师辅导等多种方式,提升储备人才的综合能力。同时,我们关注人才激励机制,确保储备人才在企业中保持积极向上的状态。',
+      '4. 人才梯队建设:我们帮助企业构建多层级、多领域的人才梯队,确保企业在关键岗位出现空缺时,能够迅速找到合适的内部候选人填补。',
+      '5. 人才跟踪与优化:门墩儿猎头将持续关注储备人才的发展状况,为企业提供人才优化建议,确保人才储备策略与企业发展战略同步调整。'
+    ]
+  },
+  {
+    title: '管理者能力提升方案',
+    startDesc: '门墩儿猎头深刻认识到,优秀的管理者是推动企业持续发展的关键因素。为此,我们精心打造了一套管理者能力提升方案,旨在帮助企业提升管理团队的整体素质,增强企业的管理效能,从而在复杂多变的市场环境中保持领先地位。',
+    startTitle: '门墩儿猎头的管理者能力提升方案包括以下核心内容:',
+    endDesc: '门墩儿猎头的管理者能力提升方案,不仅帮助企业培养了一批具有国际视野和本土实战经验的管理人才,还为企业注入了强大的管理动力,助力企业在激烈的市场竞争中保持领先地位,实现长远发展。我们坚信,通过门墩儿猎头的专业服务,企业的管理团队将不断迈向新的高度。',
+    children: [
+      '1. 能力诊断:我们通过问卷调查、一对一访谈、案例分析等多种方式,全面评估企业管理者的现有能力水平,精准识别管理者在领导力、战略规划、团队建设、执行力等方面的优势和短板。',
+      '2. 定制化培训:根据能力诊断结果,门墩儿猎头为企业量身定制培训计划,涵盖领导力发展、管理技能提升、创新思维训练等多个模块,确保培训内容与企业实际需求紧密结合。',
+      '3. 实战演练:我们通过模拟实战、案例分析、角色扮演等互动式教学方法,让管理者在实践中学习管理技巧,提升解决实际问题的能力。',
+      '4. 资深导师辅导:门墩儿猎头拥有一批经验丰富的行业导师,他们将为企业管理者提供一对一的辅导,分享成功的管理经验,帮助管理者快速成长。',
+      '5. 成果转化与评估:我们关注培训成果的转化,通过定期的跟进评估,确保管理者将所学知识应用到实际工作中,实现个人能力与企业业绩的双提升。'
+    ]
+  },
+  {
+    title: '卓越成长轨迹',
+    startDesc: '门墩儿猎头的卓越成长轨迹服务,是一项旨在帮助企业规划和实现关键人才职业发展路径的全方位服务。该服务通过精心设计的人才成长路线图,助力企业培育和保留核心人才,确保企业在不断变化的市场环境中保持竞争优势和持续发展的动力。',
+    startTitle: '以下是卓越成长轨迹服务的详细描述:',
+    endDesc: '门墩儿猎头的卓越成长轨迹服务,不仅为企业打造了一支高素质、高效率的人才队伍,也为企业创造了一个持续学习、不断进步的组织文化。我们的服务帮助企业实现人才与企业共同成长,为企业的长远发展奠定了坚实的人才基础。',
+    children: [
+      '1. 个人潜力评估:门墩儿猎头运用专业的评估工具和方法,对企业的关键人才进行深入的潜力分析,识别他们的优势领域和发展需求,为每位人才定制个性化的成长轨迹。',
+      '2. 职业发展规划:我们根据企业的战略目标和人才的特点,为关键人才制定清晰的职业发展规划,包括短期和长期的职业目标、关键能力提升点以及预期的成长里程碑。',
+      '3. 成长路径设计:门墩儿猎头帮助企业设计多样化的成长路径,包括岗位轮换、项目参与、导师制度、海外派遣等,确保人才在各个阶段都能获得必要的经验和技能。',
+      '4. 能力培养实施:我们协助企业实施能力培养计划,通过内部培训、外部学习、在线课程等多种形式,全面提升人才的专业技能和管理水平。',
+      '5. 成长跟踪与反馈:门墩儿猎头提供持续的成长跟踪服务,定期评估人才的发展进度,给予及时的反馈和建议,确保成长轨迹的顺利实施。',
+      '6. 成果转化:我们强调将人才成长转化为企业成果,通过人才的能力提升推动企业业绩的增长和竞争力的提升。'
+    ]
+  },
+  {
+    title: '高管信誉与资质验证',
+    startDesc: '门墩儿猎头深知企业在招募高级管理人员时,高管信誉与资质的重要性。为此,我们推出了高管信誉与资质验证服务,这是一项专业的背景调查和资质审核服务,旨在为企业提供准确、可靠的高管候选人评估,确保企业能够招募到具备优秀职业道德和过硬专业能力的高层领导。',
+    startTitle: '以下是高管信誉与资质验证服务的详细描述:',
+    endDesc: '门墩儿猎头的高管信誉与资质验证服务,为企业招募高管提供了坚实的安全保障,帮助企业规避了因高管个人问题带来的风险,确保了企业高层管理的稳定性和企业声誉的完整性。通过我们的专业服务,企业能够更加放心地选拔和任用高层管理人才,为企业的长远发展保驾护航。',
+    children: [
+      '1. 信誉调查:门墩儿猎头通过严格的调查流程,对高管候选人的职业背景、个人声誉、法律记录等进行深入调查,包括但不限于工作经历、教育背景、信用记录、社会关系等,以确保候选人的信誉良好。',
+      '2. 资质审核:我们对候选人的专业资质进行详细审核,包括专业资格证书、行业认证、荣誉奖项等,确保候选人的专业能力符合企业的要求。',
+      '3. 工作表现评估:门墩儿猎头通过与候选人前任雇主、同事、下属的沟通,了解其在以往工作中的表现和领导风格,为企业的决策提供参考。',
+      '4. 道德风险评估:我们评估候选人是否存在潜在的商业道德风险,如利益冲突、不正当竞争等,为企业规避潜在的风险。',
+      '5. 综合报告:门墩儿猎头根据调查和审核结果,提供一份详尽的综合报告,为企业决策提供客观、全面的参考依据。',
+      '6. 法律合规性:我们的服务严格遵守相关法律法规,确保调查过程的合法性、合规性,保护候选人的隐私权益。'
+    ]
+  }
+]

+ 0 - 12
src/utils/position.js

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

+ 3 - 3
src/utils/statisticsHeaders.js

@@ -12,7 +12,7 @@ export default {
   0: [
     { title: '职位名称', key: 'name', sortable: false },
     { title: '职位类型', key: 'positionName', sortable: false },
-    { title: '薪酬', key: 'payFrom', sortable: false, value: item => `${item.payFrom}-${item.payTo}/${item.payName}` },
+    { title: '薪酬', key: 'payFrom', sortable: false, value: item => item.payFrom && item.payTo ? `${item.payFrom ? item.payFrom + '-' : ''}${item.payTo}${item.payName ? '/' + item.payName : ''}` : '面议' },
     { title: '工作地区', key: 'areaName', sortable: false },
     { title: '工作经验', key: 'expName', sortable: false },
     { title: '学历要求', key: 'eduName', sortable: false },
@@ -21,7 +21,7 @@ export default {
   // 职位浏览量
   1: [
     { title: '招聘职位', key: 'name', sortable: false },
-    { title: '薪酬', key: 'payFrom', sortable: false, value: item => `${item.payFrom}-${item.payTo}/${item.payName}` },
+    { title: '薪酬', key: 'payFrom', sortable: false, value: item => item.payFrom && item.payTo ? `${item.payFrom ? item.payFrom + '-' : ''}${item.payTo}${item.payName ? '/' + item.payName : ''}` : '面议' },
     { title: '工作地区', key: 'areaName', sortable: false },
     { title: '工作经验', key: 'expName', sortable: false },
     { title: '学历要求', key: 'eduName', sortable: false },
@@ -32,7 +32,7 @@ export default {
     { title: '投递人', key: 'person.name', sortable: false },
     { title: '求职状态', key: 'person.jobStatus', sortable: false, value: item => jobStatus.find(i => i.value === item.person.jobStatus)?.label },
     { title: '投递岗位', key: 'job.name', sortable: false },
-    { title: '薪酬', key: 'job.payFrom', sortable: false, value: item => `${item.job.payFrom}-${item.job.payTo}/${item.job.payName}` },
+    { title: '薪酬', key: 'job.payFrom', sortable: false, value: item => item.job.payFrom && item.job.payTo ? `${item.job.payFrom ? item.job.payFrom + '-' : ''}${item.job.payTo}${item.job.payName ? '/' + item.job.payName : ''}` : '面议' },
     { title: '工作地区', key: 'job.areaName', sortable: false },
     { title: '工作经验', key: 'job.expName', sortable: false },
     { title: '学历要求', key: 'job.eduName', sortable: false },

+ 0 - 1
src/views/common/loginDialog.vue

@@ -34,7 +34,6 @@ const phoneRef = ref()
 const loginLoading = ref(false)
 
 const handleLogin = async () => {
-  localStorage.removeItem('currentRole')
   const { valid } = await phoneRef.value.phoneForm.validate()
   if (!valid) return
   loginLoading.value = true

+ 0 - 60
src/views/headhunting copy/components/form.vue

@@ -1,60 +0,0 @@
-<template>
-  <div style="width: 100%;">
-    <CtForm ref="formPageRef" :items="items"></CtForm>
-  </div>
-</template>
-
-<script setup>
-defineOptions({name: 'headhunting-form'})
-import { reactive, ref } from 'vue'
-
-const formPageRef = ref()
-let query = reactive({})
-
-const items = ref({
-  options: [
-    {
-      type: 'text',
-      key: 'name',
-      value: '',
-      default: null,
-      label: '姓名 *',
-      outlined: true,
-      rules: [v => !!v || '请输入姓名']
-    },
-    {
-      type: 'phoneNumber',
-      key: 'phone',
-      value: '',
-      clearable: true,
-      label: '联系手机号 *',
-      rules: [v => !!v || '请填写联系手机号']
-    },
-    {
-      type: 'text',
-      key: 'enterpriseName',
-      value: '',
-      default: null,
-      label: '企业名称 *',
-      outlined: true,
-      rules: [v => !!v || '请输入企业名称']
-    },
-  ]
-})
-
-
-const getQuery = async () => {
-  const { valid } = await formPageRef.value.formRef.validate()
-  if (!valid) return false
-  const obj = {}
-  items.value.options.forEach(e => {
-    obj[e.key] = e.value
-  })
-  query = Object.assign(query, obj)
-  return query
-}
-
-defineExpose({
-  getQuery
-})
-</script>

+ 0 - 209
src/views/headhunting copy/index.vue

@@ -1,209 +0,0 @@
-<!-- 门墩儿猎寻服务 -->
-<template>
-  <div class="default-width">
-    <div class="d-flex align-center" style="position: relative;">
-      <div style="width: 150px; height: 80px;">
-        <v-img src="../../assets/logo.png"  aspect-ratio="16/9" width="150" height="80"></v-img>
-      </div>
-      <h2 class="ml-10">高端猎寻服务</h2>
-      <div class="highlight-color contact" @click="showDialog = true">联系我们</div>
-    </div>
-
-    <div class="mt-5" style="width: 100%; height: 600px;">
-      <img src="https://minio.citupro.com/dev/menduner/headhunting.jpg">
-    </div>
-
-    <div class="mt-10">
-      <h1 class="text-center highlight-color">公司简介</h1>
-      <div class="d-flex">
-        <div class="text-center" style="margin-right: 50px;">
-          <v-img width="250" height="300" aspect-ratio="16/9" src="https://minio.citupro.com/dev/menduner/ceo.jpg"></v-img>
-          <div>田森先生(Simon Tian)</div>
-        </div>
-        <div style="flex: 1;">
-          <p style="text-indent: 2em; line-height: 2em;" class="mt-5">门墩儿招聘平台(mendunerhr.com)是一家专注于酒店与泛服务业的职业发展与招聘网络平台。该平台由行业资深人士创立,
-            信息技术和大数据,为酒店业的高端人才提供职业发展服务,并为企业客户提供人才解决方案和战略建议。以下是对门墩儿招聘平台的详细介绍:</p>
-          <div class="mt-3">
-            <div v-for="(k, i) in introduce" :key="i" class="mb-5">
-              <h3 class="mb-3">{{ k.title }}</h3>
-              <ul v-for="(j, index) in k.children" :key="index">
-                <li>{{ j }}</li>
-              </ul>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-
-    <!-- 精英猎头用门墩儿猎寻,猎寻优质人选 -->
-    <div class="my-10">
-      <h1 class="text-center highlight-color">精英猎头用门墩儿猎寻,猎寻优质人选</h1>
-      <div class="d-flex mt-3">
-        <v-img src="https://minio.citupro.com/dev/menduner/example.png" contain width="600" height="400"></v-img>
-        <div class="mt-7 ml-10" style="flex: 1">
-          <div v-for="(k, i) in list" :key="i" class="d-flex flex-column align-start mb-7">
-            <h2>{{ k.title }}</h2>
-            <div class="color-999 font-size-15 mt-3">{{ k.desc }}</div>
-          </div>
-        </div>
-      </div>
-    </div>
-
-    <!-- 成功案例 -->
-    <div>
-      <h1 class="text-center highlight-color">门墩儿的成功案例</h1>
-      <div class="mt-5">
-        <div v-for="(k, i) in successCases" :key="i" class="mb-10">
-          <div style="line-height: 2em;">
-            <span class="font-weight-bold">{{ k.title }}</span>
-            {{ k.desc }}
-          </div>
-        </div>
-      </div>
-    </div>
-
-    <!-- 和TA们一起 使用门墩儿猎寻招聘吧 -->
-    <div>
-      <h1 class="text-center highlight-color">和TA们一起 使用门墩儿猎寻招聘吧</h1>
-      <v-img src="https://minio.citupro.com/dev/menduner/ad.jpg" contain width="1184" height="300" class="img-ad"></v-img>
-    </div>
-
-    <div class="mt-10 d-flex align-center justify-space-between" style="border-top: 1px solid #ccc; height: 150px;">
-      <div>
-        <div style="width: 150px; height: 80px;">
-          <v-img src="../../assets/logo.png"  aspect-ratio="16/9" width="150" height="80"></v-img>
-        </div>
-      </div>
-      <div class="d-flex">
-        <div>
-          <v-img :width="100" cover aspect-ratio="16/9" src="https://minio.citupro.com/dev/static/mendunerCode.jpg" style="height: 100px;"></v-img>
-          <div class="mt-2" style="text-align: center;">微信公众号</div>
-        </div>
-        <div class="ml-10">
-          <v-img :width="100" cover aspect-ratio="16/9" src="https://minio.citupro.com/dev/menduner/miniProgram.jpg" style="height: 100px;"></v-img>
-          <div class="mt-2" style="text-align: center;">火苗儿小程序</div>
-        </div>
-      </div>
-    </div>
-
-    <CtDialog
-      :visible="showDialog"
-      :widthType="2"
-      titleClass="text-h6"
-      title="门墩儿猎寻服务咨询"
-      @submit="handleSubmit"
-      @close="showDialog = false"
-    >
-      <formItem ref="formRef"></formItem>
-    </CtDialog>
-  </div>
-</template>
-
-<script setup>
-import { ref } from 'vue'
-import formItem from './components/form.vue'
-import Curtain from '@/plugins/curtain'
-import { huntSubmit } from '@/api/headhunting'
-import { useI18n } from '@/hooks/web/useI18n'; const { t } = useI18n()
-defineOptions({ name: 'headhunting-index'})
-
-const list = [
-  { title: '高效猎寻人才', desc: '招聘服务:平台只能推荐+猎头顾问精准搜索,高效猎寻' },
-  { title: '拓宽猎企服务', desc: '企业服务:猎企接单+平台接单结合,提供猎头顾问收益' },
-  { title: '高效管理列企', desc: '职位管理、资源管理、数据看板一体的企业管理系统' }
-]
-
-// 公司简介
-const introduce = [
-  {
-    title: '平台背景与定位',
-    children: [
-      '创建背景:门墩儿平台具有丰富酒店从业经验的田森先生(Simon Tian)一手创办。Simon Tian凭借多年的行业经验和人脉,致力于推动酒店行业的发展,为高端人才和企业用户提供优质的社交平台与专业职场发展服务。',
-      '平台定位:门墩儿是首家面向酒店从业者、酒店雇主和业主的职业发展及招聘网络平台。它不仅服务于酒店业的经理或总监级以上人员,也支持青年英才,为他们提供定制职业规划,助力职业发展。'
-    ]
-  },
-  {
-    title: '服务内容',
-    children: [
-      '招聘服务:门墩儿积极携手酒店管理公司及业主等企业客户,通过名誉顾问委员会和导师委员会,提供人才解决方案和战略建议。平台从庞大的人才储备库中筛选最为匹配的人才,满足企业客户的招聘需求。',
-      '信息整合与发布:门墩儿通过其官方渠道(如微信公众号、网站等),将全国酒店的空缺职位、新任命、新酒店、行业观察等不同方向的信息与分散的资源与需求进行集中整合,成为从业者们了解行业动态、找寻发展机会的贴心资讯与服务平台。'
-    ]
-  },
-  {
-    title: '发展历程与成就',
-    children: [
-      '职业发展服务:门墩儿通过定制职业规划和人才筛选,为酒店行业的高端人才提供职业发展服务。平台汇聚了国内酒店业的资深人士,形成了一个高质量的社交网络,帮助从业者拓展人脉、获取行业资讯和职业发展机会。',
-      '取得成就:过去几年中,门墩儿平台已与多家酒店企业合作,助力了数千名酒店人的职业发展。同时,平台还通过线上及线下的全方位服务,全面覆盖酒店业精英人群,助力企业雇主品牌的不断提升。'
-    ]
-  },
-  {
-    title: '未来发展',
-    children: [
-      '门墩儿平台将继续秉承“酒店人服务酒店人”的理念,稳扎稳打,助力中国酒店人的职业发展。同时,平台还将不断拓展服务领域和范围,为酒店及泛服务业提供更加全面、专业的职业发展与招聘服务。在未来,门墩儿有望成为行业内领先的职业发展与招聘平台之一,为行业的持续发展贡献更多的力量。',
-    ]
-  },
-  {
-    title: '产品服务',
-    children: [
-      '通过战略布局与产品迭代,门墩儿逐渐形成多样化模式为核心,科技赋能的Saas产品矩阵。',
-    ]
-  }
-]
-
-// 成功案例
-const successCases = [
-  { title: '案例背景:', desc: '某知名科技公司(以下简称“A公司”)在快速发展的过程中,遇到了技术领军人才短缺的瓶颈。为了突破这一限制,A公司决定寻求专业的猎头服务,以快速定位并吸引行业内的顶尖技术人才。在众多猎头公司中,A公司选择了以其专业、高效和精准著称的“门墩儿猎头服务”。' },
-  { title: '需求分析:', desc: '门墩儿猎头服务的专业顾问团队首先与A公司的HR团队进行了深入的沟通,详细了解了A公司的企业文化、业务模式、技术需求以及对候选人的具体要求。通过全面的需求分析,确定了猎寻目标为具有丰富行业经验、精通前沿技术、并具备良好团队协作能力的技术总监。' },
-  { title: '市场调研:', desc: '基于需求分析的结果,门墩儿猎头服务启动了广泛的市场调研。他们利用自己强大的人才数据库、行业资源网络和先进的搜索技术,在全国范围内甚至全球范围内搜寻符合A公司要求的候选人。' },
-  { title: '候选人筛选与评估:', desc: '候选人筛选与评估:经过细致的筛选和初步评估,门墩儿猎头服务从众多候选人中挑选出了几位最具潜力的候选人。随后,他们安排了专业的面试评估流程,包括技能测试、行为面试、案例分析等环节,以全面评估候选人的综合素质和与A公司的匹配度。' },
-  { title: '推荐与谈判:', desc: '在确认了几位合适的候选人后,门墩儿猎头服务向A公司提交了详细的候选人推荐报告,并安排了候选人与A公司的直接面谈。在面谈过程中,门墩儿猎头服务的顾问团队全程陪同,提供专业的建议和支持,帮助双方更好地了解彼此。最终,A公司成功录用了其中一位技术总监' },
-  { title: '后续跟进:', desc: '在候选人入职后,门墩儿猎头服务并未停止服务。他们继续与A公司和候选人保持联系,关注候选人的工作表现和发展情况,并提供必要的支持和协助。同时,他们也定期与A公司沟通,收集反馈意见,以便不断优化和改进猎头服务。' },
-]
-
-const showDialog = ref(false)
-
-// 提交
-const formRef = ref()
-const handleSubmit = async () => {
-  try {
-    const obj = await formRef.value.getQuery()
-    if (!obj) return
-    const params = JSON.parse(JSON.stringify(obj))
-    await huntSubmit(params)
-    showDialog.value = false
-    Curtain('message', {
-      message: t('headhunting.submitSuccess'),
-      name: 'submit',
-      iconFontSize: 300
-    })
-  } catch (error) {
-    console.error('error', error)
-  }
-}
-
-</script>
-
-<style scoped lang="scss">
-.highlight-color {
-  color: #007eb4;
-}
-// 毛玻璃效果
-.img-ad {  
-  display: block;
-  width: 100%;
-  height: auto;
-  filter: blur(2px);
-}
-.contact {
-  position: absolute;
-  right: 0;
-  top: 22px;
-  font-weight: bold;
-  font-size: 24px;
-  cursor: pointer;
-  text-decoration: underline;
-}
-ul li {
-  list-style: none;
-}
-</style>

+ 26 - 8
src/views/headhunting/components/content.vue

@@ -19,7 +19,12 @@
     <div class="service-link">
       <div class="service-link-box common-width">
         <ul>
-          <li v-for="(val, index) in service" :key="index" :style="{'border-left': index !== 0 ? '1px solid #56738f' : 'none'}">{{ val.title }}</li>
+          <li 
+            v-for="(val, index) in service" 
+            :key="index" 
+            :style="{'border-left': index !== 0 ? '1px solid #56738f' : 'none'}"
+          >{{ val.title }}</li>
+            <!-- @click="handleClick('service', val.title)" -->
         </ul>
       </div>
     </div>
@@ -83,10 +88,16 @@
       <div class="d-flex">
         <div class="left">
           <h2>欢迎关注,了解门墩儿的新动态。</h2>
-          <p class="mt-3" style="font-size: .9rem;">史宾沙在所有主流社交媒体都有账号。欢迎关注我们,以便了解关于发展趋势和领导力问题的最新见解。</p>
+          <p class="mt-3" style="font-size: .9rem;">门墩儿在所有主流社交媒体都有账号。欢迎关注我们,以便了解关于发展趋势和领导力问题的最新见解。</p>
           <div class="social-contact">
             <div v-for="(item, index) in social" :key="index" class="d-flex" :class="{'mb-5': index !== social.length - 1}">
-              <v-img :src="item.url" width="52" height="48"></v-img>
+              <v-img :src="item.url" width="52" height="48" class="cursor-pointer"></v-img>
+              <v-tooltip v-if="item.showTooltip" :text="item.tips" location="start" activator="parent">
+                <div class="ma-3" style="text-align: center">
+                  <v-img class="ml-2" cover aspect-ratio="1/1" src="https://minio.citupro.com/dev/static/mendunerCode.jpg" width="120" height="120"></v-img>
+                  <span class="tips-text">关注门墩儿微信公众号</span>
+                </div>
+              </v-tooltip>
               <p class="ml-5">{{ item.desc }}</p>
             </div>
           </div>
@@ -109,7 +120,7 @@ const list = [
 ]
 // 服务
 const service = [
-  { title: '高级管理精英甄选' },
+  { title: '高级管理精英甄选', id: 'selection-senior-management-elite' },
   { title: '人才发展储备策略' },
   { title: '管理者能力提升方案' },
   { title: '卓越成长轨迹' },
@@ -135,9 +146,13 @@ const articles = [
 ]
 // 联系我们
 const social = [
-  { desc: '敬请查询我们的领英账号以关注最新的研究报告和 职业机会。', url: 'https://cn.spencerstuart.com/-/media/linkedin-logo.png?h=50&amp;w=50&amp;la=zh-CN&amp;hash=80E76CEDF468709061F46EE02219B4DA' },
-  { desc: '关注史宾沙官方微信号@SpencerStuart,获悉热点资讯和行业洞见。', url: 'https://cn.spencerstuart.com/-/media/wechat.jpg?h=75&amp;w=75&amp;la=zh-CN&amp;hash=B9D5E58A097630C3C5A878D5ED49B4A6' }
+  { desc: '敬请查询我们的领英账号以关注最新的研究报告和职业机会。', url: 'https://cn.spencerstuart.com/-/media/linkedin-logo.png?h=50&amp;w=50&amp;la=zh-CN&amp;hash=80E76CEDF468709061F46EE02219B4DA' },
+  { showTooltip: true, desc: '关注门墩儿官方微信公众号,获悉热点资讯和行业洞见。', url: 'https://cn.spencerstuart.com/-/media/wechat.jpg?h=75&amp;w=75&amp;la=zh-CN&amp;hash=B9D5E58A097630C3C5A878D5ED49B4A6' }
 ]
+
+// const handleClick = (type, title) => {
+//   window.open()
+// }
 </script>
 
 <style scoped lang="scss">
@@ -218,7 +233,6 @@ const social = [
   font-style: normal;
   font-weight: 700;
   color: #fff;
-  font-size: .9rem;
   letter-spacing: 3px;
   text-transform: uppercase;
   padding: 20px 0;
@@ -233,7 +247,7 @@ const social = [
       padding: 0;
       li {
         width: 20%;
-        padding: 15px;
+        padding: 13px;
         text-align: center;
         vertical-align: middle;
         cursor: pointer;
@@ -506,4 +520,8 @@ const social = [
     flex: 1;
   }
 }
+.tip-text {
+  font-size: 14px;
+  color: var(--color-222);
+}
 </style>

+ 3 - 3
src/views/headhunting/components/serviceContent.vue

@@ -119,7 +119,7 @@ const list = [
   padding: 15px 15px 11px;
   text-align: center;
   min-width: 220px;
-  border: 2px solid #338441;
+  border: 2px solid #00695c;
   background-color: transparent;
   color: #4c4c4e;
   outline: 0;
@@ -129,7 +129,7 @@ const list = [
   &:hover {
     color: #fff;
     font-weight: 700;
-    background-color: #338441;
+    background-color: #00695c;
   }
 }
 .common-line {
@@ -226,7 +226,7 @@ const list = [
       &:hover {
         color: #fff;
         font-size: 12px;
-        background-color: #003d79;
+        background-color: #00695c;
       }
       .service-title {
         font-weight: 700;

+ 18 - 0
src/views/headhunting/details.vue

@@ -0,0 +1,18 @@
+<template>
+  <div>xxx</div>
+</template>
+
+<script setup>
+defineOptions({ name: 'headhuntingDetails' })
+import { useRouter } from 'vue-router'
+
+const router  = useRouter()
+router.beforeEach((to, from, next) => {
+  document.title = to.meta.title
+  next()
+})
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 4 - 1
src/views/integral/pointsManagement/components/integralShow.vue

@@ -5,7 +5,10 @@
       <div v-for="(val, i) in list" :key="i" :style="{'margin-left': val.showRules ? '0' : '200px'}">
         <div>
           <span class="ml-10 item-title">{{ val.title }}</span>
-          <span v-if="val.showRules" class="ml-2 rules cursor-pointer" @click="integralRulesClick">{{ $t('points.integralRules') }}</span>
+          <span v-if="val.showRules" class="ml-5 rules cursor-pointer" @click="integralRulesClick">
+            <v-icon>mdi-help-circle-outline</v-icon>
+            {{ $t('points.integralRules') }}
+          </span>
         </div>
         <div>
           <span v-if="val.value === 'balance'" class="ml-10 item-value">{{ accountData[val.value] && accountData[val.value] > 0 ? (accountData[val.value]) : 0 }}</span>

+ 114 - 0
src/views/login/components/editPassword.vue

@@ -0,0 +1,114 @@
+<template>
+  <div>
+    <PhonePage ref="phoneRef" style="width: 370px;" :phone="phone" :phoneDisabled="phone ? true : false"></PhonePage>
+    <v-form ref="passwordRef" style="width: 370px;">
+      <v-text-field
+        v-model="query.password"
+        placeholder="请输入新密码" 
+        variant="outlined" 
+        density="compact"
+        color="primary"
+        prepend-inner-icon="mdi-lock-outline" 
+        :append-inner-icon="passwordType ? 'mdi-eye-outline' : 'mdi-eye-off-outline'"
+        :type="passwordType ? 'text' : 'password'"
+        :rules="[v=> !!v || '请填写新密码', validPassword]"
+        @click:append-inner="passwordType = !passwordType"
+      ></v-text-field>
+      <v-text-field
+        v-model="query.checkPassword"
+        placeholder="请再次输入新密码" 
+        variant="outlined" 
+        density="compact"
+        color="primary"
+        prepend-inner-icon="mdi-lock-outline" 
+        :append-inner-icon="show ? 'mdi-eye-outline' : 'mdi-eye-off-outline'"
+        :type="show ? 'text' : 'password'"
+        :rules="[v=> !!v || '请再次输入新密码', passwordCheck]"
+        @click:append-inner="show = !show"
+      ></v-text-field>
+    </v-form>
+    <div class="text-center mt-5">
+      <v-btn v-if="showCancelBtn" class="mr-5" color="primary" variant="outlined" @click="handleClose">取 消</v-btn>
+      <v-btn color="primary" :max-width="showCancelBtn ? 370 : 95" :min-width="showCancelBtn ? 95 : 370" @click="handleSubmit" :loading="loading">确认修改</v-btn>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'editPasswordCommonPage'})
+import { ref, reactive, computed } from 'vue'
+import PhonePage from '@/components/VerificationCode'
+import { updatePassword, resetPassword } from '@/api/common/index'
+import Snackbar from '@/plugins/snackbar'
+
+const emit = defineEmits(['cancel'])
+const props = defineProps({
+  phone: {
+    type: String,
+    default: ''
+  },
+  showCancelBtn: {
+    type: Boolean,
+    default: true
+  },
+  isReset: {
+    type: Boolean,
+    default: false
+  }
+})
+
+let query = reactive({
+  password: '',
+  checkPassword: ''
+})
+const passwordRef = ref(false)
+const show = ref(false)
+const loading = ref(false)
+const passwordType = ref(false)
+const phoneRef = ref()
+
+// 密码效验
+const regex = /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]).{8,16}$/
+const validPassword = computed(() => {
+  return regex.test(query.password) || '请输入8-16位数由数字、大小写字母组成的密码'
+})
+const passwordCheck = computed(() => {
+  return (query.checkPassword === query.password && regex.test(query.checkPassword)) || '两次密码输入不一致'
+})
+
+
+const handleClose = () => {
+  query = {
+    password: '',
+    checkPassword: ''
+  }
+  passwordType.value = false
+  loading.value = false
+  emit('cancel')
+}
+
+// 修改
+const handleSubmit = async () => {
+  const passwordValid = await passwordRef.value.validate()
+  const phoneValid = await phoneRef.value.phoneForm.validate()
+  if (!passwordValid.valid || !phoneValid.valid) return
+  const data = {
+    password: query.password,
+    code: phoneRef.value.loginData.code
+  }
+  // 重置密码需要手机号
+  if (props.isReset) data.phone = phoneRef.value.loginData.phone
+  loading.value = true
+  const api = props.isReset ? resetPassword : updatePassword
+  try {
+    await api(data)
+    Snackbar.success('修改成功')
+  } finally {
+    loading.value = false
+    handleClose()
+  }
+}
+</script>
+
+<style scoped lang="scss">
+</style>

+ 19 - 18
src/views/login/components/passwordPage.vue

@@ -1,7 +1,8 @@
 <template>
   <v-form ref="passwordForm" @submit.prevent>
-    <v-text-field v-model="loginData.phone" counter="11" :disabled="props.phoneDisabled" :placeholder="$t('login.mobileNumberPlaceholder')" color="primary" variant="outlined" density="compact" :rules="phoneRules" validate-on="input">
-      <template v-slot:prepend-inner>
+    <v-text-field v-model="loginData.phone" :disabled="props.phoneDisabled" placeholder="请输入手机号码(企业请输入邮箱登录)" color="primary" 
+    variant="outlined" density="compact" :rules="phoneRules" validate-on="input" prepend-inner-icon="mdi-cellphone" >
+      <!-- <template v-slot:prepend-inner>
         <span class="d-flex">
           <v-icon icon="mdi-cellphone" size="20"></v-icon>
           <span class="d-flex" id="menu-activator">
@@ -16,7 +17,7 @@
             </v-list>
           </v-menu>
         </span>
-      </template>
+      </template> -->
     </v-text-field>
     <v-text-field
       v-model="loginData.password"
@@ -37,8 +38,8 @@
 <script setup name="passwordPage">
 import { ref, reactive } from 'vue'
 defineOptions({ name: 'password-form' })
-import { useI18n } from '@/hooks/web/useI18n'
-const { t } = useI18n()
+// import { useI18n } from '@/hooks/web/useI18n'
+// const { t } = useI18n()
 const props = defineProps({ phoneDisabled: Boolean })
 const passwordType = ref(false)
 const emits = defineEmits(['handleEnter'])
@@ -46,22 +47,22 @@ const emits = defineEmits(['handleEnter'])
 const phoneRules = ref([
   value => {
     if (value) return true
-    return t('login.mobileNumberPlaceholder')
-  },
-  value => {
-    if (value?.length <= 11 && /^1[3456789]\d{9}$/.test(value)) return true
-    return t('login.correctPhoneNumber')
+    return '请输入手机号码或邮箱'
   }
+  // value => {
+  //   if (value?.length <= 11 && /^1[3456789]\d{9}$/.test(value)) return true
+  //   return t('login.correctPhoneNumber')
+  // }
 ])
 
 // 手机号区域
-const currentArea = ref('0086')
-const items = [
-  { label: '中国大陆-0086', value: '0086' }
-]
-const handleChangeCurrentArea = (e) => {
-  currentArea.value = e.value
-}
+// const currentArea = ref('0086')
+// const items = [
+//   { label: '中国大陆-0086', value: '0086' }
+// ]
+// const handleChangeCurrentArea = (e) => {
+//   currentArea.value = e.value
+// }
 
 const loginUserPhone = localStorage.getItem('loginUserPhone') || ''
 const loginData = reactive({
@@ -72,7 +73,7 @@ const loginData = reactive({
 // 设置默认账号密码便于开发快捷登录
 if (window.location.hostname === 'localhost' || window.location.hostname === '192.168.3.152') {
   loginData.phone = '13229740092'
-  loginData.password = 'Citu123'
+  loginData.password = 'Citu123456'
 }
 
 const passwordForm = ref()

+ 38 - 0
src/views/login/forgotPassword.vue

@@ -0,0 +1,38 @@
+<template>
+  <div class="box">
+    <div class="content pa-10">
+      <div class="resume-header">
+        <div class="resume-title">修改密码</div>
+      </div>
+      <editPasswordPage class="mt-5" :showCancelBtn="false" :isReset="true" @cancel="router.push('/login')"></editPasswordPage>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'forgotPassword'})
+import { useRouter } from 'vue-router'
+import editPasswordPage from '@/views/login/components/editPassword.vue'
+
+const router = useRouter()
+</script>
+
+<style scoped lang="scss">
+.box {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  background-image: url('https://www.mendunerhr.com/images/userfiles/92d7e4a755e2428b94aab3636d5047f3/images/recruitment/adImages/2018/11/1920x940.jpg');
+  background-size: cover;
+}
+.content {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  translate: -50% -50%;
+  width: 450px;
+  height: 450px;
+  background-color: #fff;
+  border-radius: 10px;
+}
+</style>

+ 39 - 160
src/views/login/index.vue

@@ -1,111 +1,62 @@
 <template>
   <div class="login-box">
-    <div class="login-content" :style="{height: loginType ? '492px' : '430px'}">
-      <div class="login-header mt-1 ml-1">
-        <div class="left">
-          <div ref="phone" :class="['left-qrCode', {'phone-switch': isPhone}]" @click="handlePhone">
-            <div class="switch-tip">
-              {{ isPhone ? $t('login.smsOrPassword') : $t('login.scanWeChatCode') }}
-            </div>
-          </div>
-          <div v-if="loginType" class="loginType">
-            <span>{{ $t('login.enterpriseLogin') }}</span>
-            <v-tooltip :text="$t('login.switchToPersonalLogin')" location="start">
-              <template v-slot:activator="{ props }">
-                <v-btn
-                  class="ml-0"
-                  color="#fffff"
-                  size="x-small"
-                  icon="mdi-swap-vertical"
-                  variant="plain"
-                  v-bind="props"
-                  to="/login"
-                  @click="switchToPersonalLogin"
-                >
-                </v-btn>
-              </template>
-            </v-tooltip>
-            <!-- <span class="mdi mdi-swap-vertical ml-4"></span> -->
-          </div>
-        </div>
-        <div class="right mr-2 mt-3" v-if="showClose">
-          <v-icon color="grey" size="30">mdi-close</v-icon>
-        </div>
+    <div class="login-content">
+      <div class="text-end pr-5 pt-5">
+        <span class="color-error cursor-pointer text-decoration-underline" @click="router.push('/register/selected')">还没有登录账户?去注册</span>
       </div>
-      <div class="login-content-box mt-5">
-        <div v-if="!isPhone" class="login-tab">
-          <v-tabs v-model="tab" align-tabs="center" color="primary">
-            <v-tab :value="1">{{ $t('login.smsLogin') }}</v-tab>
-            <v-tab :value="2">{{ $t('login.passwordLogin') }}</v-tab>
+      <div class="login-content-box mb-10">
+        <div class="login-tab">
+          <v-tabs v-model="tab" align-tabs="center" color="primary" class="mb-10">
+            <v-tab :value="1">验证码</v-tab>
+            <v-tab :value="2">账号</v-tab>
+            <v-tab :value="3">二维码</v-tab>
           </v-tabs>
-          <div v-if="loginType" class="mt-9">
-            <!-- 企业选择 -->
-            <companySelect ref="companySelectRef" v-model="enterpriseId"></companySelect>
-          </div>
-          <v-window v-model="tab" :class="{'mt-9': !loginType}">
+          <v-window v-model="tab">
               <!-- 验证码登录 -->
             <v-window-item :value="1">
-              <phoneFrom ref="phoneRef" :phoneDisabled="Boolean(loginType)" @handleEnter="handleLogin"></phoneFrom>
+              <phoneFrom ref="phoneRef" @handleEnter="handleLogin"></phoneFrom>
             </v-window-item>
               <!-- 账号密码登录 -->
             <v-window-item :value="2">
-              <passwordFrom ref="passRef" :phoneDisabled="Boolean(loginType)" @handleEnter="handleLogin"></passwordFrom>
+              <passwordFrom ref="passRef" @handleEnter="handleLogin"></passwordFrom>
+            </v-window-item>
+            <v-window-item :value="3">
+              <div class="d-flex align-center flex-column">
+                <span class="text-decoration-underline">微信扫描二维码进行登录</span>
+                <v-img src="https://minio.citupro.com/dev/menduner/login-qrCode.png" width="150" height="150"></v-img>
+              </div>
             </v-window-item>
           </v-window>
         </div>
-        <div v-else>
-          <!-- 微信扫码登录 -->
-          <div v-if="loginType" class="mt-9">
-            <companySelect v-model="enterpriseId"></companySelect>
-          </div>
-          <qr-code></qr-code>
+        <div class="font-size-14 tips">
+          <span class="float-left color-666 cursor-pointer" v-if="tab === 2" @click="router.push('/forgotPassword')">忘记密码</span>
         </div>
-        <v-btn v-if="!isPhone" :loading="loginLoading" color="primary" class="white--text mt-5" min-width="350" @click="handleLogin">
-          {{ tab === 1 ? $t('login.loginOrRegister') : $t('login.login') }}
+        <v-btn :loading="loginLoading" color="primary" class="white--text mt-5" min-width="350" @click="handleLogin">
+          {{ $t('login.login') }}
         </v-btn>
         <div class="login-tips mt-3">
           {{ $t('login.agreeLogin') }}
-          <span class="color" style="cursor: pointer;" @click="handleToUserAgreement">[{{ $t('login.userAgreement') }}]</span>
+          <span class="color" style="cursor: pointer;" @click="handleToUserAgreement"> [{{ $t('login.userAgreement') }}] </span>
           <span class="color" style="cursor: pointer;" @click="handlePrivacyPolicy">[{{ $t('login.privacyPolicy') }}]</span>
         </div>
       </div>
     </div>
   </div>
-  <!-- <CtDialog :visible="showCompanySelect" title="选择企业" :footer="true" widthType="" @submit="handleSubmit">
-    <div  style="min-height: 10px">
-      <autocompleteUI></autocompleteUI>
-    </div>
-  </CtDialog> -->
 </template>
 
 <script setup>
 import { ref } from 'vue'
-import { getUserBindEnterpriseListByPhone } from '@/api/personal/user'
 import passwordFrom from './components/passwordPage.vue'
 import phoneFrom from '@/components/VerificationCode'
-import qrCode from './components/qrCode.vue'
-import companySelect from './components/companySelect.vue'
-
 import { useUserStore } from '@/store/user'
-import { useRoute, useRouter } from 'vue-router'
+import { useRouter } from 'vue-router'
 import { useI18n } from '@/hooks/web/useI18n'
 import Snackbar from '@/plugins/snackbar'
 defineOptions({ name: 'login-index' })
 
 const { t } = useI18n()
 const router = useRouter()
-const route = useRoute()
-const loginType = ref(route.query?.loginType - 0 || null)
-const enterpriseId = ref('')
-
-const phone = ref()
-let isPhone = ref(false)
-const handlePhone = () => {
-  isPhone.value = !isPhone.value
-  phone.value.style.backgroundPosition = isPhone.value ? '0 -80px' : '0 0'
-}
 const tab = ref(1)
-const showClose = ref(false)
 
 // 验证码登录
 const phoneRef = ref()
@@ -113,36 +64,27 @@ const passRef = ref()
 const loginLoading = ref(false)
 const userStore = useUserStore()
 
-const companySelectRef = ref()
-const beforeHandleLogin = async (params) => {
-  const data = await getUserBindEnterpriseListByPhone({ phone: params.phone}) // 申请通过才会数据,否则空数组
-  if (companySelectRef.value.item?.items) companySelectRef.value.item.items = data
-  // localStorage.setItem('companyInfo', JSON.stringify(data))
-  const bool = Boolean(data?.length)
-  if (!bool) Snackbar.warning(t('login.loginFailed'))
-  return bool
-}
 const handleLogin = async () => {
-  localStorage.removeItem('currentRole')
   const { valid } = tab.value === 1 ? await phoneRef.value.phoneForm.validate() : await passRef.value.passwordForm.validate()
   if (!valid) return
   loginLoading.value = true
   try {
-    const type = loginType.value
     let params, api = {}
     if (tab.value === 1) { params = { ...phoneRef.value.loginData }; api = 'handleSmsLogin'}
     else { params = { ...passRef.value.loginData }; api = 'handlePasswordLogin'}
-    // 企业登录
-    if (type === 330) {
-      const bool = await beforeHandleLogin(params); if (!bool) return
-      params.loginType = type; params.enterpriseId = enterpriseId.value
+    
+    // 邮箱为企业招聘, 手机号为个人求职
+    if (tab.value === 2) {
+      const pattern = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
+      const isEnterprise = pattern.test(params.phone)
+      params.isEnterprise = isEnterprise
     }
-    // await userStore.handlePasswordLogin({ ...passRef.value.loginData, type }) //tab.value === 1
-    // await userStore.handleSmsLogin({ ...phoneRef.value.loginData, type })
+    if (params.isEnterprise) router.push({ path: '/enterpriseVerification' }) // 先跳转到会使用企业token的路由
     await userStore[api](params)
+    // 跳转
+    if (params.isEnterprise) return // 企业邮箱登录
     Snackbar.success(t('login.loginSuccess'))
-    const path = type === 330 ? '/recruit/enterprise' : '/recruitHome'
-    router.push({ path })
+    router.push({ path: '/recruitHome' })
   }
   finally {
     loginLoading.value = false
@@ -156,26 +98,9 @@ const handleToUserAgreement = () => {
 const handlePrivacyPolicy = () => {
   router.push({ path: '/privacyPolicy' })
 }
-
-const switchToPersonalLogin = () => {
-  loginType.value = 0
-  Snackbar.success(t('common.switchSuccessful'))
-}
 </script>
 
 <style lang="scss" scoped>
-.loginType {
-  position: absolute;
-  top: 16px;
-  right: 0;
-  // width: 100%;
-  color: #fff;
-  padding: 4px 15px 4px 32px;
-  border-radius: 8px 0 0 8px;
-  // background-color: #ffba5d;
-  background-color: #fa9c3e;
-  font-size: 16px;
-}
 .login-box {
   position: relative;
   width: 100%;
@@ -189,71 +114,25 @@ const switchToPersonalLogin = () => {
   left: 50%;
   translate: -50% -50%;
   width: 450px;
-  // height: 430px;
   background-color: #fff;
   border-radius: 10px;
 }
-.login-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  height: 40px;
-}
 .login-content-box {
   padding: 0 50px;
 }
 .left {
   display: flex;
 }
-.left-qrCode {
-  position: absolute;
-  left: 8px;
-  top: 8px;
-  width: 40px;
-  height: 40px;
-  cursor: pointer;
-  background: url('../../assets/login.png') 0 0/40px auto no-repeat;
-}
-.left-qrCode:hover {
-  background-position: 0 -40px !important;
-}
-.left-qrCode.phone-switch {
-  background-position: 0 -80px !important;
-}
-.left-qrCode.phone-switch:hover {
-  background-position: 0 -120px !important;
-}
-.switch-tip {
-  position: absolute;
-  left: 60px;
-  top: 4px;
-  padding: 0 14px;
-  border-radius: 4px;
-  font-size: 12px;
-  color: var(--color-666);
-  line-height: 32px;
-  text-align: center;
-  background-color: #fff;
-  box-shadow: 0 6px 13px 0 rgba(0,0,0,.1);
-  white-space: nowrap;
-  &::before {
-    content: '';
-    position: absolute;
-    left: -5px;
-    top: 50%;
-    transform: translateY(-50%) rotate(45deg);
-    width: 10px;
-    height: 10px;
-    background-color: #fff;
-    box-shadow: 0 6px 13px 0 rgba(0,0,0,.1);
-  }
-}
 .login-tips {
   width: 100%;
   font-size: 12px;
   text-align: center;
 }
-
+.tips {
+  span:hover {
+    text-decoration: underline;
+  }
+}
 .color {
   color: var(--v-primary-base); 
 }

+ 1 - 1
src/views/publicRecruitment/components/table.vue

@@ -33,7 +33,7 @@ const headers = [
   { title: '牛人', value: 'sendPerson.name', key: 'name', sortable: false },
   { title: '应聘公司', key: 'enterprise.anotherName', sortable: false },
   { title: '应聘职位', key: 'job', value: item => item?.job?.name, sortable: false },
-  { title: '岗位薪资', key: 'salary', value: item => `${item?.job?.payFrom}-${item?.job?.payTo}`, sortable: false },
+  { title: '岗位薪资', key: 'salary', value: item => item?.job?.payFrom && item?.job?.payTo ? `${item?.job?.payFrom ? item?.job?.payFrom + '-' : ''}${item?.job?.payTo}` : '面议', sortable: false },
   { title: '推荐时间', key: 'createTime', value: item => timesTampChange(item.createTime), sortable: false },
 ]
 

+ 7 - 5
src/views/recruit/components/message/components/chatting.vue

@@ -29,7 +29,8 @@
         <div class="d-flex justify-space-between">
           <div class="font-weight-bold color-primary">
             <span>{{ val.job.name }}</span>
-            <span class="ml-3">{{ val.job.payFrom }}-{{ val.job.payTo }}</span>
+            <span v-if="!val.job.payFrom && !val.job.payTo" class="ml-3">面议</span>
+            <span v-else class="ml-3">{{ val.job.payFrom ? val.job.payFrom + '-' : '' }}{{ val.job.payTo }}</span>
           </div>
           <div :style="{'color': ['5', '98', '99'].includes(val.status) ? 'var(--v-error-base)' : 'var(--v-primary-base)'}">{{ statusList.find(e => e.value === val.status)?.label }}</div>
         </div>
@@ -70,7 +71,8 @@
             >
               <div class="pa-3">
                 <div class="text-h6"> {{ val.payload?.content?.positionInfo?.name }}</div>
-                <div class="text-subtitle-2">薪酬待遇: {{ val.payload?.content?.positionInfo?.payFrom }} - {{ val.payload?.content?.positionInfo?.payTo }}</div>
+                <div v-if="!val.payload?.content?.positionInfo?.payFrom && !val.payload?.content?.positionInfo?.payTo" class="text-subtitle-2">薪酬待遇: 面议</div>
+                <div v-else class="text-subtitle-2">薪酬待遇: {{ val.payload?.content?.positionInfo?.payFrom ? val.payload?.content?.positionInfo?.payFrom + ' - ' : '' }}{{ val.payload?.content?.positionInfo?.payTo }}</div>
                 <div>
                   <v-chip
                     color="secondary"
@@ -173,8 +175,9 @@
                     <div class="text-h6 mb-1">
                       {{ val.payload?.content?.positionInfo?.data?.name }}
                     </div>
-                    <div>
-                      {{ val.payload?.content?.positionInfo?.data?.payFrom }} - 
+                    <div v-if="!val.payload?.content?.positionInfo?.data?.payFrom && !val.payload?.content?.positionInfo?.data?.payTo">面议</div>
+                    <div v-else>
+                      {{ val.payload?.content?.positionInfo?.data?.payFrom ? val.payload?.content?.positionInfo?.data?.payFrom + ' - ' : '' }}
                       {{ val.payload?.content?.positionInfo?.data?.payTo }}
                     </div>
                   </div>
@@ -227,7 +230,6 @@ import { useIMStore } from '@/store/im'
 import { useI18n } from '@/hooks/web/useI18n'
 import { useRouter } from 'vue-router';
 import { getDict } from '@/hooks/web/useDictionaries'
-// import { getDictValueWithLabel } from '@/utils/position'
 import { getUserAvatar } from '@/utils/avatar'
 
 import { useUserStore } from '@/store/user'

+ 2 - 1
src/views/recruit/components/message/index.vue

@@ -441,8 +441,9 @@ async function handleInvite (item) {
     if (!data.length) return
     const list = dealDictArrayData([], data)
     positionList.value = list.map(e => {
+      const salary = e.payFrom && e.payTo ? `${e.payFrom ? e.payFrom + '-' : ''}${e.payTo}${e.payName ? '/' + e.payName : ''}` : '面议'
       return {
-        label: `${e.name}${e.areaName ? '_' + e.areaName : ''} ${e.payFrom}-${e.payTo}/${e.payName}`,
+        label: `${e.name}${e.areaName ? '_' + e.areaName : ''} ${salary}`,
         value: e.id,
         data: e
       }

+ 1 - 1
src/views/recruit/enterprise/register/inReview.vue → src/views/recruit/entRegister/inReview.vue

@@ -46,7 +46,7 @@
         </div>
         <div class="text-center" v-if="!isMobile">
           <v-btn class="mt-16 buttons" color="primary" to="/recruitHome">{{ $t('common.toHome') }}</v-btn>
-          <v-btn v-if="applyInfo.status === '2'" class="mt-16 ml-12 buttons" color="primary" to="/recruit/enterprise/register">{{ $t('common.resubmit') }}</v-btn>
+          <v-btn v-if="applyInfo.status === '2'" class="mt-16 ml-12 buttons" color="primary" to="/recruit/entRegister">{{ $t('common.resubmit') }}</v-btn>
         </div>
       </div>
     </v-card>

+ 1 - 1
src/views/recruit/enterprise/register/joiningEnterprise.vue → src/views/recruit/entRegister/joiningEnterprise.vue

@@ -21,7 +21,7 @@
       </div>
       <!-- 底部 -->
       <div class="text-center mt-5">
-        <v-btn color="primary" variant="text" @click="router.push({ path: '/recruit/enterprise/register' })">{{ $t('enterprise.registeringNewEnterprise') }}</v-btn>
+        <v-btn color="primary" variant="text" @click="router.push({ path: '/recruit/entRegister' })">{{ $t('enterprise.registeringNewEnterprise') }}</v-btn>
       </div>
     </v-card>
   </div>

+ 59 - 21
src/views/recruit/enterprise/register/register.vue → src/views/recruit/entRegister/register.vue

@@ -1,12 +1,10 @@
 <template>
-  <div class="my-5">
-    <div :class="isMobile? 'mobileBox' : 'default-width'">
-      <v-btn class="my-2" color="primary" variant="text" size="large" @click="router.push('/recruitHome')">{{ `<< 回到首页` }}</v-btn>
-    </div>
+  <div class="login-box py-5">
     <v-card class="pa-5" :class="isMobile? 'mobileBox' : 'default-width'" :elevation="isMobile? '0' : '3'">
       <!-- 标题 -->
-      <div class="resume-header">
-        <div class="resume-title">{{ $t('enterprise.registeringNewEnterprise') }}</div>
+      <div class="mt-3">
+        <v-btn v-if="pageType !== 'noLoginToRegister'" color="black" variant="text" @click="router.push('/recruitHome')">{{ `<< 回到首页` }}</v-btn>
+        <div v-else style="height: 30px;"></div>
       </div>
       <!-- 表单 -->
       <div class="CtFormClass" :style="{width: isMobile ? '' : '600px'}">
@@ -14,6 +12,8 @@
           <span class="mr-5">《审核不通过》</span>
           <span>原因:{{ failureReason }}</span>
         </div>
+        <!-- 标题 -->
+        <div class="mb-10 mt-n8" style="font-size: 22px; font-weight: bold; text-align: center;">{{ $t('enterprise.registeringNewEnterprise') }}</div>
         <CtForm ref="CtFormRef" :items="formItems" style="width: 100%;">
           <template #prepare>
             <v-checkbox
@@ -30,7 +30,6 @@
         <div style="color: var(--color-999);">
           <span v-if="!isPrepare" class="mr-1" style="color: var(--v-error-base);">*</span>
           <span>上传营业执照</span>
-          <!-- <span class="mx-3 defaultLink">查看示例</span> -->
           <span>支持jpg、jpeg、png格式,图片大小不得超过10M</span>
         </div>
         <div class="file-box">
@@ -39,10 +38,6 @@
         <div class="note mt-10">
           <h4>注意事项:</h4>
           <span>企业名称为对外展示的企业名称,建议填写公司营业执照上的名称,请区分总公司和分公司</span>
-          <!-- <div>
-            <span>2.若公司已注册,请上传公司委托证明下载模版</span>
-            <span class="mx-3 defaultLink">下载模版</span>
-          </div> -->
         </div>
       </div>
       <div class="text-center">
@@ -55,10 +50,6 @@
         {{ $t('common.complete') }}
         </v-btn>
       </div>
-      <!-- 底部 个人不能绑定企业 要去后端管理员加 -->
-      <!-- <div class="text-center">
-        <v-btn color="primary" variant="text" @click="router.push({ path: '/recruit/enterprise/register/joiningEnterprise' })">{{ $t('enterprise.joiningEnterprise') }}</v-btn>
-      </div> -->
     </v-card>
     <PreviewImg v-if="showPreview" :current="current" :list="[licenseUrl]" @close="showPreview = !showPreview"></PreviewImg>
   </div>
@@ -88,6 +79,10 @@ onMounted(() => {
   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)
 })
 
+
+import { useRoute } from 'vue-router'; const route = useRoute()
+const pageType = route?.query?.type || '' // type: noLoginToRegister:->登录页注册企业
+
 // 是否筹建中
 const isPrepare = ref(false)
 const isPrepareChange = () => {
@@ -122,6 +117,8 @@ const formItems = ref({
       key: 'contactName',
       value: '',
       label: '联系人姓名 *',
+      // col: 6,
+      // flexStyle: 'mr-3',
       rules: [v => !!v || '请输入联系人姓名']
     },
     {
@@ -129,17 +126,40 @@ const formItems = ref({
       key: 'phone',
       value: '',
       label: '联系电话 *',
-      // flexStyle: 'mr-3',
-      // col: 6,
       rules: [v => !!v || '请输入联系电话']
     },
     {
       type: 'text',
       key: 'email',
       value: '',
-      label: '联系邮箱 *',
-      // col: 6,
-      rules: [v => !!v || '请输入联系邮箱']
+      label: '联系邮箱(可用于企业招聘登录) *',
+      rules: [
+        value => {
+          if (value) return true
+          return '请输入联系邮箱'
+        },
+        value => {
+          const pattern = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
+          if (pattern.test(value)) return true
+          return '请输入邮箱'
+        }
+      ]
+    },
+    {
+      type: 'text',
+      key: 'password',
+      value: '',
+      label: '邮箱登录密码(用于企业招聘邮箱登录) *',
+      placeholder: '请输入邮箱登录密码(用于企业招聘邮箱登录)',
+      rules: [v => !!v || '请输入邮箱登录密码(用于企业招聘邮箱登录)']
+    },
+    {
+      type: 'text',
+      key: 'passwordConfirm',
+      value: '',
+      label: '请再次输入邮箱登录密码 *',
+      placeholder: '请再次输入邮箱登录密码',
+      rules: [v => !!v || '请再次输入邮箱登录密码']
     },
     {
       type: 'textarea',
@@ -157,10 +177,15 @@ const formItems = ref({
 // 上传
 let licenseUrl = ref('')
 
+const isOnlySpaces = (str) => {
+  return /^[\s]+$/.test(str) ? null : str // 判断是否只有空格
+}
+
 // 提交 企业注册
 const handleCommit = async () => {
   const { valid } = await CtFormRef.value.formRef.validate()
   if (!valid) return
+
   const businessLicenseUrl = licenseUrl.value;
   if (!isPrepare.value && !businessLicenseUrl) return Snackbar.warning('请上传营业执照图片')
   const params = {
@@ -168,9 +193,15 @@ const handleCommit = async () => {
     prepare: isPrepare.value,
   }
   formItems.value.options.forEach(e => { params[e.key] = e.value })
+  // 邮箱登录密码校验
+  const pa1 = isOnlySpaces(params.password); const pa2 = isOnlySpaces(params.passwordConfirm);
+  if (pa1 && !pa2) return Snackbar.warning('请输入确认邮箱登录密码')
+  if (!pa1 && pa2) return Snackbar.warning('请输入邮箱登录密码')
+  if (pa1 !== pa2) return Snackbar.warning('两次输入的邮箱登录密码不一致,请确认输入内容')
+
   await enterpriseRegisterApply(params)
   Snackbar.success(t('common.submittedSuccessfully'))
-  router.push({ path: '/recruit/enterprise/register/inReview' })
+  router.push({ path: '/recruit/entRegister/inReview' })
 }
 
 // 不通过的企业注册申请 重新发起
@@ -195,6 +226,13 @@ if (info && Object.keys(info).length && info.status === '2') {
   font-size: 14px;
   line-height: 32px;
 }
+.login-box {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  background-image: url('https://www.mendunerhr.com/images/userfiles/92d7e4a755e2428b94aab3636d5047f3/images/recruitment/adImages/2018/11/1920x940.jpg');
+  background-size: cover;
+}
 .file-box {
   display: flex;
   flex-wrap: wrap; /* 允许换行 */

+ 2 - 1
src/views/recruit/enterprise/enterpriseCenter/components/positions.vue

@@ -54,7 +54,8 @@
           </div>
         </div>
         <div v-if="!val.active" class="text-right">
-          <p class="salary">{{ val.job.payFrom }}-{{ val.job.payTo }}/{{ val.job.payName }}</p>
+          <p v-if="!val.job.payFrom && !val.job.payTo" class="salary">面议</p>
+          <p v-else class="salary">{{ val.job.payFrom ? val.job.payFrom + '-' : '' }}{{ val.job.payTo }}{{ val.job.payName ? '/' + val.job.payName : '' }}</p>
           <div class="update-time">{{ timesTampChange(val.job.updateTime) }} 刷新</div>
         </div>
         <div v-else class="account-info">

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

@@ -15,7 +15,8 @@
             <span class="lines"></span>
             <span>{{ val.expName }}</span>
             <span class="lines"></span>
-            <span>{{ val.payFrom }}-{{ val.payTo }}/{{ val.payName }}</span>
+            <span v-if="!val.payFrom && !val.payTo">面议</span>
+            <span v-else>{{ val.payFrom ? val.payFrom + '-' : '' }}{{ val.payTo }}{{ val.payName ? '/' + val.payName : '' }}</span>
             <span class="lines"></span>
             <span>{{ val.positionName }}</span>
           </div>

+ 1 - 1
src/views/recruit/enterprise/interviewManagement/index.vue

@@ -157,7 +157,7 @@ const getPositionList = async () => {
   if (!data.length) return
   const list = dealDictArrayData([], data)
   positionItems.value = list.map(e => {
-    return { label: `${e.name}${e.areaName ? '_' + e.areaName : ''} ${e.payFrom}-${e.payTo}/${e.payName}`, value: e.id }
+    return { label: `${e.name}${e.areaName ? '_' + e.areaName : ''} ${e.payFrom ? e.payFrom + '-' : ''}${e.payTo}${e.payName ? '/' + e.payName : ''}`, value: e.id }
   })
 }
 getPositionList()

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

@@ -72,7 +72,7 @@ const formItems = ref({
 
 if (Object.keys(props.itemData).length) {
   const obj = formItems.value.options.find(e => e.key === 'position')
-  obj.value = `${props.itemData?.job?.name}${props.itemData?.job?.areaName ? '_' + props.itemData?.job?.areaName : ''} ${props.itemData?.job?.payFrom}-${props.itemData?.job?.payTo}/${props.itemData?.job?.payName}`
+  obj.value = `${props.itemData?.job?.name}${props.itemData?.job?.areaName ? '_' + props.itemData?.job?.areaName : ''} ${props.itemData?.job?.payFrom ? props.itemData?.job?.payFrom + '-' : ''}${props.itemData?.job?.payTo}${props.itemData?.job?.payName ? '/' + props.itemData?.job?.payName : ''}`
   formItems.value.options.find(e => e.key === 'address').value = props.itemData.job?.address
 }
 

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

@@ -87,7 +87,7 @@ list.value.forEach(k => {
       if (data.length) {
         const list = dealDictArrayData([], data)
         k.items = list.map(e => {
-          return { label: `${e.name}${e.areaName ? '_' + e.areaName : ''} ${e.payFrom}-${e.payTo}/${e.payName}`, value: e.id }
+          return { label: `${e.name}${e.areaName ? '_' + e.areaName : ''} ${e.payFrom ? e.payFrom + '-' : ''}${e.payTo}${e.payName ? '/' + e.payName : ''}`, value: e.id }
         })
       }
     })

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

@@ -75,7 +75,7 @@ const headers = ref([
   { title: '求职状态', key: 'person.jobStatusName', sortable: false },
   { title: '工作经验', key: 'person.expName', sortable: false },
   { title: '最高学历', key: 'person.eduName', sortable: false },
-  { title: '岗位薪资', key: 'job', value: item => `${item.job.payFrom}-${item.job.payTo}/${item.job.payName}`, sortable: false },
+  { title: '岗位薪资', key: 'job', value: item => item.job.payFrom && item.job.payTo ? `${item.job.payFrom ? item.job.payFrom + '-' : ''}${item.job.payTo}${item.job.payName ? '/' + item.job.payName : ''}` : '面议', sortable: false },
   { title: '状态', key: 'status', sortable: false, value: item => item.status ? props.statusList.find(i => i.value === item.status).label : '' },
   { title: '操作', value: 'actions', sortable: false }
 ])

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

@@ -26,7 +26,8 @@
             <span class="lines"></span>
             <span>{{ val.expName }}</span>
             <span class="lines"></span>
-            <span>{{ val.payFrom }}-{{ val.payTo }}/{{ val.payName }}</span>
+            <span v-if="!val.payFrom && !val.payTo">面议</span>
+            <span v-else>{{ val.payFrom ? val.payFrom + '-' : '' }}{{ val.payTo }}{{ val.payName ? '/' + val.payName : '' }}</span>
             <span class="lines"></span>
             <span>{{ val.positionName }}</span>
           </div>

+ 1 - 1
src/views/recruit/enterprise/resumeManagement/elite/components/invite.vue

@@ -84,7 +84,7 @@ const formItems = ref({
 
 if (Object.keys(props.itemData).length) {
   const obj = formItems.value.options.find(e => e.key === 'position')
-  obj.value = `${props.itemData?.job?.name}${props.itemData?.job?.areaName ? '_' + props.itemData?.job?.areaName : ''} ${props.itemData?.job?.payFrom}-${props.itemData?.job?.payTo}/${props.itemData?.job?.payName}`
+  obj.value = `${props.itemData?.job?.name}${props.itemData?.job?.areaName ? '_' + props.itemData?.job?.areaName : ''} ${props.itemData?.job?.payFrom ? props.itemData?.job?.payFrom + '-' : ''}${props.itemData?.job?.payTo}${props.itemData?.job?.payName ? '/' + props.itemData?.job?.payName : ''}`
   formItems.value.options.find(e => e.key === 'address').value = props.itemData.job?.address
 
   const baseInfo = localStorage.getItem('entBaseInfo')

+ 1 - 1
src/views/recruit/enterprise/resumeManagement/elite/components/public.vue

@@ -83,7 +83,7 @@ const formItems = ref({
 
 if (Object.keys(props.itemData).length) {
   const obj = formItems.value.options.find(e => e.key === 'position')
-  obj.value = `${props.itemData?.job?.name}${props.itemData?.job?.areaName ? '_' + props.itemData?.job?.areaName : ''} ${props.itemData?.job?.payFrom}-${props.itemData?.job?.payTo}/${props.itemData?.job?.payName}`
+  obj.value = `${props.itemData?.job?.name}${props.itemData?.job?.areaName ? '_' + props.itemData?.job?.areaName : ''} ${props.itemData?.job?.payFrom ? props.itemData?.job?.payFrom + '-' : ''}${props.itemData?.job?.payTo}${props.itemData?.job?.payName ? '/' + props.itemData?.job?.payName : ''}`
   formItems.value.options.find(e => e.key === 'address').value = props.itemData.job?.address
 }
 

+ 1 - 1
src/views/recruit/enterprise/resumeManagement/elite/components/screen.vue

@@ -94,7 +94,7 @@ list.value.forEach(k => {
       if (data.length) {
         const list = dealDictArrayData([], data)
         k.items = list.map(e => {
-          return { label: `${e.name}${e.areaName ? '_' + e.areaName : ''} ${e.payFrom}-${e.payTo}/${e.payName}`, value: e.id }
+          return { label: `${e.name}${e.areaName ? '_' + e.areaName : ''} ${e.payFrom ? e.payFrom + '-' : ''}${e.payTo}${e.payName ? '/' + e.payName : ''}`, value: e.id }
         })
       }
     })

+ 11 - 2
src/views/recruit/enterprise/resumeManagement/elite/components/table.vue

@@ -31,6 +31,7 @@
         <v-btn v-if="tab === 0" color="primary" variant="text" @click="handleInterviewInvite(item)">邀请面试</v-btn>
         <v-btn v-if="tab === 0" color="primary" variant="text" @click="handleToCommunicate(item)">立即沟通</v-btn>
         <v-btn v-if="tab === 0 || tab === 1" color="primary" variant="text" @click="handleEliminate(item)">不合适</v-btn>
+        <v-btn v-if="!item.inTalentPool && (tab === 1 || tab === 2 || tab === 3)" color="primary" variant="text" @click="handleJoinToTalentPool(item)">加入人才库</v-btn>
         <v-btn v-if="tab === 1 && (item.status === '3' || item.status === '4')" color="primary" variant="text" @click="handleEnterByEnterprise(item)">入职</v-btn>
         <v-btn v-if="tab === 4" color="primary" variant="text" @click="handleCancelEliminate(item)">取消不合适</v-btn>
         <v-btn v-if="tab === 2 && item?.job?.hire" color="primary" variant="text" @click="handleSettlement(item)">结算</v-btn>
@@ -49,7 +50,7 @@
 defineOptions({ name: 'table-page'})
 import { ref, computed, watch } from 'vue'
 import { previewFile } from '@/utils'
-import { personJobCvLook, joinEliminate, personEntryByEnterprise, personCvUnfitCancel } from '@/api/recruit/enterprise/personnel'
+import { personJobCvLook, joinEliminate, personEntryByEnterprise, personCvUnfitCancel, joinToTalentPool } from '@/api/recruit/enterprise/personnel'
 import { saveInterviewInvite } from '@/api/recruit/enterprise/interview'
 import { hireJobCvRelSettlement } from '@/api/recruit/public/delivery'
 import { useI18n } from '@/hooks/web/useI18n'
@@ -86,7 +87,7 @@ const headers = ref([
   { title: '求职状态', key: 'person.jobStatusName', sortable: false },
   { title: '工作经验', key: 'person.expName', sortable: false },
   { title: '最高学历', key: 'person.eduName', sortable: false },
-  { title: '岗位薪资', key: 'job', value: item => `${item.job.payFrom}-${item.job.payTo}/${item.job.payName}`, sortable: false },
+  { title: '岗位薪资', key: 'job', value: item => `${item.job.payFrom ? item.job.payFrom + '-' : ''}${item.job.payTo}${item.job.payName ? '/' + item.job.payName : ''}`, sortable: false },
   { title: '状态', key: 'status', sortable: false },
   { title: '操作', value: 'actions', sortable: false }
 ])
@@ -119,6 +120,14 @@ const handleToPersonDetail = ({ userId, id }) => {
   window.open(`/recruit/enterprise/resumeManagement/talentPool/details/${userId}?id=${id}`)
 }
 
+// 加入人才库 
+const handleJoinToTalentPool = async (item) => {
+  if (!item.userId) return Snackbar.warning('数据异常')
+  await joinToTalentPool(item.userId)
+  Snackbar.success(t('common.operationSuccessful'))
+  emit('refresh')
+}
+
 // 入职
 const handleEnterByEnterprise = async (item) => {
   if (!item.id) return

+ 311 - 0
src/views/recruit/enterprise/resumeManagement/talentMap/components/filter copy.vue

@@ -0,0 +1,311 @@
+<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-map-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: '姓名 ',
+    },
+    {
+      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()
+
+formItems.value.options.forEach((e, index) => {
+  // 查字典set options
+  if (e.dictTypeName) getDictData(e)
+  // 样式
+  e.col = 6
+  e.clearable = true
+  if ((index + 2) % 2 === 0 && !e.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>

+ 22 - 211
src/views/recruit/enterprise/resumeManagement/talentMap/components/filter.vue

@@ -1,30 +1,12 @@
 <template>
-  <div class="px-5 py-3" 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="px-5 pt-1 py-15" style="position: relative;">
+    <h3 class="mt-3 my-5" style="color: var(--v-primary-base);">职位匹配</h3>
+    <CtForm ref="CtFormRef" :items="formItems" style="width: 100%;"></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" 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>
@@ -36,11 +18,6 @@
 <script setup>
 defineOptions({ name: 'talent-map-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'])
 
@@ -49,152 +26,34 @@ const CtFormRef = ref()
 const formItems = ref({
   options: [
     {
-      type: 'text',
-      key: 'name',
-      value: '',
-      label: '姓名 ',
+      type: 'textarea',
+      key: 'content',
+      value: null,
+      counter: 500,
+      rows: 5,
+      resize: true,
+      label: '匹配内容',
+      placeholder: '请输入要匹配内容',
+      clearable: true,
+      outlined: true,
     },
     {
       type: 'autocomplete',
       key: 'labels',
       value: null,
-      label: '人员标签 ',
+      label: '人标签 ',
       multiple: true,
       outlined: true,
       itemText: 'label',
       itemValue: 'value',
+      clearable: true,
       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 || []
@@ -205,73 +64,23 @@ const getLabelData = async () => {
 }
 getLabelData()
 
-formItems.value.options.forEach((e, index) => {
-  // 查字典set options
-  if (e.dictTypeName) getDictData(e)
-  // 样式
-  e.col = 6
-  e.clearable = true
-  if ((index + 2) % 2 === 0 && !e.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 (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
-    }
+    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]
-    }
+    if (query[e.key] !== undefined) e.value = query[e.key]
   })
 }
 
@@ -303,7 +112,9 @@ defineExpose({
   position: fixed;
   width: 96%;
   margin: 0 12px;
-  bottom: 14px;
+  bottom: 0;
   left: 0;
+  padding-bottom: 14px;
+  background-color: #fff;
 }
 </style>

+ 7 - 5
src/views/recruit/enterprise/resumeManagement/talentMap/index.vue

@@ -2,7 +2,7 @@
   <v-card class="card-box pa-5" style="height: 100%;">
     <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>
+      <v-btn color="primary" class="half-button" variant="tonal" @click="openDrawer">职位匹配</v-btn>
     </div>
     <!-- 人员信息表单 -->
     <v-data-table
@@ -11,6 +11,8 @@
       :headers="headers"
       hover
       :disable-sort="true"
+      :loading="loading"
+      loading-text="Loading... Please wait"
       item-value="id"
     >
       <template #bottom></template>
@@ -68,6 +70,7 @@ import FilterPage from './components/filter.vue'
 import { computed, reactive, ref } from 'vue'
 
 const screen = ref(false)
+const loading = ref(false)
 
 let query = {}
 const pageInfo = reactive({ pageNo: 1, pageSize: 10 })
@@ -93,12 +96,12 @@ const headers = [
 // 获取数据
 const getData = async () => {
   const obj = { ...pageInfo, ...query }
-  console.log('obj', obj)
+  // console.log('obj', obj)
+  loading.value = true
   const { list, total: number } = await getRecruitPersonMapPage(obj)
-  // const { list, total: number } = dataTest
-  //
   total.value = number
   dataList.value = list?.length ? dealDictArrayData([], list) : []
+  loading.value = false
 }
 getData()
 
@@ -122,7 +125,6 @@ const advantageDetail = (advantage) => {
   advantageShow.value = true
 }
 
-
 const FilterPageRef = ref()
 const openDrawer = () => {
   screen.value = true

+ 2 - 1
src/views/recruit/enterprise/resumeManagement/talentPool/components/details/jobIntention.vue

@@ -8,7 +8,8 @@
       <div class="d-flex">
         <div>{{ k.position }}</div>
         <div class="line">|</div>
-        <div>{{k.payFrom}}-{{k.payTo}}</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>

+ 295 - 102
src/views/recruit/enterprise/resumeManagement/talentPool/components/filter.vue

@@ -1,107 +1,189 @@
 <template>
-  <div class="px-5 py-3" style="position: relative;">
-    <h3 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>
+  <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 #areaIds="{ item }">
+      <!-- 期望岗位 -->
+      <template #positionId="{ item }">
         <v-menu :close-delay="1" :open-delay="0" v-bind="$attrs">
           <template v-slot:activator="{  props }">
-            <TextInput
+            <textUI
               v-model="item.value"
               :item="item"
               v-bind="props"
               style="position: relative;"
-            ></TextInput>
+              @handleClear="handleJobClickItem()"
+            ></textUI>
           </template>
-          <AreaSelect :select="[query.areaIds].filter(Boolean)" @handleAreaClick="handleArea" class="jobTypeCardBox" isSingle></AreaSelect>
-        </v-menu>
-      </template>
-      <template #residence="{ item }">
-        <v-menu :close-delay="1" :open-delay="0" v-bind="$attrs">
-          <template v-slot:activator="{  props }">
-            <TextInput
-              v-model="item.value"
-              :item="item"
-              v-bind="props"
-              style="position: relative;"
-            ></TextInput>
-          </template>
-          <AreaSelect :select="[query.residence].filter(Boolean)" @handleAreaClick="handleResidence" class="jobTypeCardBox" isSingle></AreaSelect>
+          <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="text-end mt-3">
-        <v-btn class="half-button mr-3" variant="tonal">取消</v-btn>
-        <v-btn class="half-button" color="primary">确定</v-btn>
+      <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-filter'})
+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 query = ref({})
 const formItems = ref({
   options: [
     {
-      type: 'ifRadio',
+      type: 'text',
+      key: 'name',
+      value: '',
+      label: '姓名 ',
+      col: 12
+    },
+    {
+      type: 'autocomplete',
       key: 'sex',
-      value: '0',
-      default: 0,
-      label: '性别',
-      width: 90,
+      value: null,
+      default: null,
+      label: '工作经验',
+      outlined: true,
+      itemText: 'label',
+      itemValue: 'value',
       dictTypeName: 'menduner_sex',
-      items: [
-        { label: '不限', value: '0' },
-        { label: '男', value: '1' },
-        { label: '女', value: '1' }
-      ]
+      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: 'ifRadio',
-      key: 'sex',
-      value: '0',
-      default: 0,
-      label: '人才分类',
-      width: 90,
-      items: [
-        { label: '不限', value: '0' },
-        { label: '默认分类', value: '1' }
-      ]
+      type: 'text',
+      key: 'email',
+      value: '',
+      label: '联系邮箱 ',
     },
     {
-      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: '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: 'expType',
+      key: 'eduType',
       value: null,
       default: null,
       label: '学历',
-      col: 6,
       outlined: true,
       itemText: 'label',
-      flexStyle: 'mr-3',
       itemValue: 'value',
       dictTypeName: 'menduner_education_type',
       items: []
@@ -112,59 +194,168 @@ const formItems = ref({
       value: null,
       default: null,
       label: '工作经验',
-      col: 6,
       outlined: true,
       itemText: 'label',
       itemValue: 'value',
       dictTypeName: 'menduner_exp_type',
       items: []
     },
-    // {
-    //   slotName: 'residence',
-    //   key: 'residence',
-    //   value: null,
-    //   col: 6,
-    //   flexStyle: 'mr-3',
-    //   label: '户籍地'
-    // },
-    // {
-    //   slotName: 'areaIds',
-    //   key: 'areaIds',
-    //   value: null,
-    //   col: 6,
-    //   label: '期望城市'
-    // },
     {
-      type: 'vueDatePicker',
-      key: 'firstWorkTime',
+      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,
-      class: 'mb-3',
-      options: {
-        type: '',
-        range: true,
-        format: 'timestamp',
-        placeholder: '加入日期',
-      }
+      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 handleReset = () => {}
+// 获取字典内容
+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 handleArea = (list, name) => {
-  if (!list.length) return
-  query.value.workAreaId = list[0]
-  formItems.value.options.find(e => e.key === 'areaIds').value = name
+// 期望职位
+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 handleResidence = (list, name) => {
-  if (!list.length) return
-  query.value.residence = list[0]
-  formItems.value.options.find(e => e.key === 'residence').value = name
+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">
@@ -177,14 +368,16 @@ const handleResidence = (list, name) => {
 }
 .jobTypeCardBox {
   position: absolute;
-  top: -22px;
+  // top: -22px;
   left: 0;
 }
 .bottom {
   position: fixed;
   width: 96%;
   margin: 0 12px;
-  bottom: 14px;
+  bottom: 0;
   left: 0;
+  padding-bottom: 14px;
+  background-color: #fff;
 }
 </style>

+ 355 - 0
src/views/recruit/enterprise/resumeManagement/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/resumeManagement/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/resumeManagement/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>

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

@@ -1,89 +1,56 @@
 <template>
   <v-card class="card-box pa-5">
     <div class="d-flex justify-space-between">
-      <TextUI :item="textItem" @enter="handleEnter" @appendInnerClick="handleEnter"></TextUI>
-      <v-btn color="primary" prependIcon="mdi-filter-multiple-outline" class="half-button" variant="tonal" @click="screen = true">筛选</v-btn>
+      <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" 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">
-      <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">
+    <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-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>
+              offset-y="6"
+              :color="badgeColor(item)"
+              :icon="badgeIcon(item)">
+              <v-avatar size="40" :image="getUserAvatar(item.avatar, item.sex)"></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>
-          </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>
+            <span class="defaultLink ml-3">{{ item?.name }}</span>
           </div>
-        </div>
-      </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="query.pageNo"
-        :limit="query.pageSize"
+        :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></FilterPage>
+      <FilterPage
+        ref="FilterPageRef"
+        @confirm="handleConfirm"
+        @cancel="screen = false"
+      ></FilterPage>
     </v-navigation-drawer>
   </v-card>
 </template>
@@ -92,147 +59,92 @@
 // import { useRouter } from 'vue-router'
 // const router = useRouter()
 defineOptions({ name: 'enterprise-talent-pool'})
-import { ref } from 'vue'
-import TextUI from '@/components/FormUI/TextInput'
+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/resumeManagement/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 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 dataList = ref([])
+// dataList.value = [] // 暂定无数据展示
 const tipsText = ref('暂无数据')
 const total = ref(2)
-const query = ref({
-  pageNo: 1,
-  pageSize: 10
-})
+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 handleEnter = (e) => {
-  console.log(e, 'enter')
-}
+// 获取数据
+const getData = async () => {
+  const obj = { ...pageInfo, ...query }
+  console.log('obj', obj)
+  const { list, total: number } = await getTalentPoolPage(pageInfo)
+  total.value = number
+  dataList.value = list?.length ? dealDictArrayData([], list) : []
+  console.log('items.value', dataList.value)
+}
+getData()
+
 
-const dealSelect = () => {
-  selectList.value = items.value.filter(e => e.select).map(k => k.id)
+// 移出人才库 
+const handleRemove = async (item) => {
+  if (!item.userId) return Snackbar.warning('数据异常')
+  await removeFormTalentPool(item.userId)
+  Snackbar.success(t('common.operationSuccessful'))
+  getData()
 }
 
-// 全选
-const handleChangeSelectAll = () => {
-  items.value.map(k => {
-    k.select = selectAll.value
-    return k
-  })
-  dealSelect()
+const handleChangePage = (e) => {
+  pageInfo.pageNo = e
+  getData()
 }
-// 单选
-const handleChangeSelect = () => {
-  const length = items.value.filter(k => k.select).length
-  selectAll.value = length > 0 ? true : false
-  dealSelect()
+
+// 筛选
+const handleConfirm = (params) => {
+  screen.value = false
+  pageInfo.pageNo = 1
+  query = { ...params }
+  getData()
 }
 
-const handleChangePage = () => {
-  // 分页获取新数据后勾选
-  selectList.value.forEach(e => {
-    const obj = items.value.find(k => k.id === e)
-    if (obj) obj.select = true
-  })
+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

+ 1 - 1
src/views/recruit/enterprise/systemManagement/groupAccount/inviteConfirm.vue

@@ -127,7 +127,7 @@ const join = async () => {
 }
 
 const logoutFun = async () => {
-  if (!getToken()) {
+  if (!getToken(1)) {
     localStorage.clear()
     return
   }

+ 3 - 3
src/views/recruit/enterprise/systemManagement/groupAccount/inviteConfirmEnt.vue

@@ -129,7 +129,7 @@ const getEnterpriseList = async() => {
 const getApplyInfo = async () => {
   const data = await getUserRegisterEnterpriseApply()
   const bool = data && Object.keys(data).length // 已经有数据说明已经申请过了
-  const path = bool ? '/recruit/enterprise/register/inReview' : '/recruit/enterprise/register'
+  const path = bool ? '/recruit/entRegister/inReview' : '/recruit/entRegister'
   router.push({ path })
   if (!bool) Snackbar.warning('未查询到该用户下存在企业,请优先提交企业申请')
 }
@@ -160,12 +160,12 @@ const handleRegister = () => {
 }
 
 const logoutFun = async () => {
-  if (!getToken()) {
+  if (!getToken(1)) {
     localStorage.clear()
     return
   }
   try {
-    await await logoutToken(getToken())
+    await await logoutToken(getToken(1))
     localStorage.clear()
   } catch (error) {
     console.log('登出失败!', error)

+ 0 - 0
src/views/recruit/personal/accountSettings/dynamic/accountBinding.vue → src/views/recruit/personal/PersonalCenter/accountSettings/accountBinding.vue


+ 41 - 0
src/views/recruit/personal/PersonalCenter/accountSettings/editPassword.vue

@@ -0,0 +1,41 @@
+<template>
+  <div style="position: relative; width: 100%; height: 100%;">
+    <h3>{{ $t('setting.editPassword') }}</h3>
+    <v-divider class="mb-4"></v-divider>
+    <div v-if="!showEdit" class="login-user mb-4">
+      当前登录账号: 
+      <span>{{ userInfo.phone }}</span>
+      <span class="color-primary ml-5 text-decoration-underline cursor-pointer" @click="showEdit = true">修改密码</span>
+    </div>
+    <editPasswordPage v-if="showEdit" :phone="userInfo.phone" @cancel="showEdit = false" class="editPage"></editPasswordPage>
+  </div>
+</template>
+
+<script setup name="editPassword">
+import { ref } from 'vue'
+import editPasswordPage from '@/views/login/components/editPassword.vue'
+
+const showEdit = ref(false)
+
+// 当前登录的用户信息
+const userInfo = JSON.parse(localStorage.getItem('userInfo'))
+</script>
+
+<style lang="scss" scoped>
+h3 {
+  font-size: 20px;
+  text-align: left;
+  font-weight: 600;
+  padding-bottom: 25px;
+}
+.login-user {
+  color: var(--color-666);
+  font-weight: 600;
+}
+.editPage {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+}
+</style>

+ 0 - 0
src/views/recruit/personal/accountSettings/dynamic/privacySettings.vue → src/views/recruit/personal/PersonalCenter/accountSettings/privacySettings.vue


+ 0 - 0
src/views/recruit/personal/accountSettings/dynamic/realAuthentication.vue → src/views/recruit/personal/PersonalCenter/accountSettings/realAuthentication.vue


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

@@ -22,7 +22,8 @@
               <span>[{{ val.areaName }}]</span>
             </div>
             <div class="job-other">
-              <span class="salary">{{ val.payFrom }}-{{ val.payTo }}k</span>
+              <span v-if="!val.payFrom && !val.payTo" class="salary">面议</span>
+              <span v-else class="salary">{{ val.payFrom ? val.payFrom + '-' : '' }}{{ val.payTo }}</span>
               <v-chip class="mx-3" color="primary" label size="small">{{ val.expName }}</v-chip>
               <v-chip color="primary" label size="small">{{ val.eduName }}</v-chip>
             </div>

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

@@ -37,7 +37,8 @@
           </div>
           <div class="fz-14 mb-2">
             <span>薪资:</span>
-            <span class="c-base">{{ val?.job?.payFrom || '--' }}-{{ val?.job?.payTo || '--' }}/{{ val?.job?.payName || '--' }}</span>
+            <span v-if="!val?.job?.payTo && !val?.job?.payFrom" class="c-base">面议</span>
+            <span v-else class="c-base">{{ val?.job?.payFrom || '--' }}-{{ val?.job?.payTo || '--' }}/{{ val?.job?.payName || '--' }}</span>
           </div>
           <div class="img-box">
             <v-avatar size="small" :image="getUserAvatar(val.contact.avatar, val.contact.sex)"></v-avatar>

+ 0 - 187
src/views/recruit/personal/PersonalCenter/dynamic/left.vue

@@ -1,187 +0,0 @@
-<template>
-  <div>
-    <div class="left-top">
-      <v-badge 
-        bordered 
-        offset-x="10" 
-        offset-y="50" 
-        :color="baseInfo?.sex ? (baseInfo?.sex === '1' ? '#1867c0' : 'error') : 'error'" 
-        :icon="baseInfo?.sex ? (baseInfo?.sex === '1' ? 'mdi-gender-male' : 'mdi-gender-female') : 'mdi-gender-female'">
-        <v-avatar size="x-large" :image="getUserAvatar(baseInfo?.avatar, baseInfo?.sex)"></v-avatar>
-      </v-badge>
-      <div class="ml-5 content">
-        <div class="username">
-          <span class="mr-2">{{ baseInfo?.name || userInfo.phone }}</span>
-        </div>
-        <div class="userInfo">
-          {{ baseInfo?.expTypeText || $t('common.currentlyUnavailable') }}<span class="septal-line"></span>{{ baseInfo?.eduTypeText || $t('common.currentlyUnavailable') }}
-        </div>
-        <div class="mt-3">
-          <v-select 
-            v-model="selectVal" 
-            :items="items" 
-            density="compact" 
-            variant="outlined" 
-            item-title="label" 
-            item-value="value" 
-            hide-details 
-            color="primary"
-            @update:model-value="handleChangeJobStatus"
-          ></v-select>
-        </div>
-      </div>
-      <div class="otherInfo">
-        <div>
-          <span>{{ $t('resume.phoneNumber') }}:{{ baseInfo?.phone || $t('common.currentlyUnavailable') }}</span>
-        </div>
-        <div class="my-3">
-          <span>{{ $t('resume.userEmail') }}:{{ baseInfo?.email || $t('common.currentlyUnavailable') }}</span>
-        </div>
-        <div>
-          <span>{{ $t('resume.dateOfBirth') }}:{{ baseInfo?.birthdayText || $t('common.currentlyUnavailable') }}</span>
-        </div>
-        <div class="mt-3">{{ $t('resume.currentAddress') }}: {{ baseInfo?.areaName || $t('common.currentlyUnavailable') }}</div>
-      </div>
-      <div class="slider-btn">
-        <v-btn class="slider-btn-item" rounded variant="outlined" color="primary" append-icon="mdi-menu-right" to="/recruit/personal/resume">{{ $t('resume.onlineResume') }}</v-btn>
-      </div>
-    </div>
-
-    <div class="left-tabs mt-3">
-      <v-tabs v-model="tab" align-tabs="start" color="primary" bg-color="#fff">
-        <v-tab v-for="(val, i) in list" :key="i" :value="i">{{ val.title }}</v-tab>
-      </v-tabs>
-    </div>
-    <div :class="['left-bottom', {'mt-3': list[tab].path !== interview && list[tab].path !== interested}]">
-      <component :is="list[tab].path"></component>
-    </div>
-  </div>
-</template>
-
-<script setup>
-defineOptions({ name: 'personal-center-left'})
-import { getDict } from '@/hooks/web/useDictionaries'
-import { ref, watch } from 'vue'
-import { useUserStore } from '@/store/user'
-import { updateJobStatus } from '@/api/recruit/personal/resume'
-import { useI18n } from '@/hooks/web/useI18n'
-import Snackbar from '@/plugins/snackbar'
-// import communication from '../components/communication.vue'
-import delivery from '../components/delivery.vue'
-import interview from '../components/interview/index.vue'
-import interested from '../components/interested.vue'
-import seenMe from '../components/seenMe.vue'
-import { getUserAvatar } from '@/utils/avatar'
-
-const { t } = useI18n()
-const list = [
-  // { title: t('position.throughCommunication'), path: communication },
-  { title: t('position.delivered'), path: delivery },
-  { title: t('position.interview'), path: interview },
-  { title: t('position.interested'), path: interested },
-  { title: t('position.haveSeenMe'), path: seenMe }
-]
-
-const tab = ref(0)
-
-import { useRoute } from 'vue-router'; const route = useRoute()
-import { useRouter } from 'vue-router'; const router = useRouter()
-watch(() => route.query, (newQuery) => { // newQuery, oldQuery
-  if (newQuery?.showInterviewScheduleMore) { // 去掉面试日程参数
-    tab.value = 2
-    const query = { ...route.query }
-    delete query.showInterviewScheduleMore
-    const path = route.path
-    router.replace({ path, query })
-  }
-}, { deep: true, immediate: true })
-
-const selectVal = ref('0')
-const items = ref([])
-const userStore = useUserStore()
-
-// 求职类型字典
-getDict('menduner_job_status').then(({ data }) => {
-  data = data?.length && data || []
-  items.value = data
-})
-
-let baseInfo = ref({})
-let userInfo = ref({})
-const getBasicInfo = () => {
-  baseInfo.value = JSON.parse(localStorage.getItem('baseInfo')) // 人才信息
-  userInfo.value = JSON.parse(localStorage.getItem('userInfo')) // 人才信息
-  selectVal.value = baseInfo?.value?.jobStatus || '0'
-}
-getBasicInfo()
-
-// 求职类型更换
-const handleChangeJobStatus = async () => {
-  await updateJobStatus(selectVal.value)
-  Snackbar.success('更新成功')
-  await userStore.getUserBaseInfos(baseInfo.value.userId)
-  getBasicInfo()
-}
-</script>
-
-<style scoped lang="scss">
-.left-top {
-  display: flex;
-  align-items: center;
-  padding: 12px 0 12px 12px;
-  height: 144px;
-  border-radius: 12px;
-  background-color: #fff;
-  .content {
-    height: 100%;
-    .username {
-      font-weight: 500;
-      font-size: 20px;
-      color: var(--color-222);
-      height: 24px;
-      line-height: 24px;
-    }
-    .userInfo {
-      height: 21px;
-      margin-top: 8px;
-      color: var(--color-666);
-      font-size: 14px;
-    }
-  }
-  .otherInfo {
-    flex: 1;
-    height: 100%;
-    margin-left: 50px;
-    font-size: 14px;
-    font-weight: 500;
-    color: var(--color-333);
-    text-align: left;
-    .salary {
-      font-size: 16px;
-      font-family: 'kanzhun-Regular';
-      font-weight: 500;
-      color: var(--v-error-base);
-      margin: -1px 0 0 16px;
-    }
-    .edu-time {
-      color: var(--color-999);
-      font-weight: 400;
-      margin-left: 20px;
-    }
-    .edu {
-      margin: 12px 0;
-    }
-  }
-  .slider-btn {
-    position: relative;
-    min-width: 188px;
-    height: 100%;
-    overflow: hidden;
-    .slider-btn-item {
-      position: absolute;
-      top: 4px;
-      right: -15px;
-    }
-  }
-}
-</style>

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

@@ -1,207 +0,0 @@
-<template>
-  <div>
-    <div class="accountBox d-flex mb-3 radius white-bgc flex-column">
-      <div class="resume-header ml-3 mt-2">
-        <div class="resume-title">{{ $t('points.wallet') }}</div>
-      </div>
-      <div class="d-flex" v-if="userAccount && Object.keys(userAccount).length">
-        <div v-for="val in accountList" :key="val.title" class="accountItem cursor-pointer" @click="router.push({ path: '/recruit/personal/myWallet' })">
-          <v-icon color="primary">{{ val.icon }}</v-icon>
-          <div class="ml-1">
-            <div v-if="val.key === 'balance'" class="title-text">{{ (userAccount[val.key] && userAccount[val.key] > 0 ? (userAccount[val.key] / 100.0).toFixed(2) : 0) + val.desc }}</div>
-            <div v-else class="title-text">{{ (userAccount[val.key] || 0) + val.desc }}</div>
-            <div class="tip-text">{{ val.title }}</div>
-          </div>
-        </div>
-      </div>
-      <div v-else class="text-center font-size-14 mb-3">
-        请先登录
-      </div>
-    </div>
-
-    <div class="resume d-flex">
-      <div v-for="val in resumeList" :key="val.title" class="topping white-bgc radius" @click="resumeClick(val)">
-        <v-icon color="primary">{{ val.icon }}</v-icon>
-        <div class="ml-1">
-          <div class="title-text">{{ val.title }}</div>
-          <div class="tip-text">{{ val.desc }}</div>
-        </div>
-      </div>
-    </div>
-    <div class="attachment white-bgc radius mt-3">
-      <div>
-        <span class="title">{{ $t('resume.attachmentResume') }}</span>
-        <span class="upload--text cursor-pointer" @click="openFileInput">
-          {{ $t('common.upload') }}
-          <File ref="uploadFile" @success="handleUploadResume"></File>
-        </span>
-      </div>
-      <span class="more-text">{{ $t('resume.uploadUpToFiveCopies') }}</span>
-      <div v-if="attachmentList.length">
-        <div class="d-flex attachment-item my-2" 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>
-          <span class="cursor-pointer color-primary" @click="previewFile(k.url)">预览</span>
-          <span class="cursor-pointer mx-2 color-primary" @click="handleDownload(k)">下载</span>
-          <span class="cursor-pointer color-error" @click="handleDelete(k)">删除</span>
-        </div>
-      </div>
-      <div v-else class="more-text d-flex justify-center">暂无简历,请先上传</div>
-    </div>
-  </div>
-</template>
-
-<script setup>
-defineOptions({ name: 'personal-center-right'})
-import { ref } from 'vue'
-import { previewFile } from '@/utils'
-import { useRouter } from 'vue-router'
-import { getPersonResumeCv, savePersonResumeCv, deletePersonResumeCv } from '@/api/recruit/personal/resume'
-import { useI18n } from '@/hooks/web/useI18n'
-import { useUserStore } from '@/store/user'
-import Snackbar from '@/plugins/snackbar'
-import Confirm from '@/plugins/confirm'
-import { getBlob, saveAs } from '@/utils'
-
-const { t } = useI18n()
-const router = useRouter()
-const userStore = useUserStore()
-
-const accountList = [
-  { icon: 'mdi-currency-cny', title: t('enterprise.account.accountBalances'), desc: t('unit.rmb'), key: 'balance' },
-  { icon: 'mdi-octagram-outline', title: t('resume.goldCoins'), desc: t('unit.ge'), key: 'point' }
-]
-let userAccount = ref(JSON.parse(localStorage.getItem('userAccount')) || {}) // 账户信息
-userStore.$subscribe((mutation, state) => {
-  userAccount.value = state.userAccount || {}
-})
-
-const resumeList = ref([
-  { name: 'refresh', icon: 'mdi-refresh', title: t('resume.refreshResume'), desc: t('resume.enhanceResumeActivity') },
-  { name: 'order', icon: 'mdi-clipboard-list-outline', title: '我的订单', desc: '交易订单' },
-])
-const resumeClick = async (val) => {
-  if (val.name === 'order') router.push('/recruit/personal/tradeOrder')
-}
-
-// 获取附件
-const attachmentList = ref([])
-const getList = async () => {
-  const data = await getPersonResumeCv()
-  attachmentList.value = data
-}
-getList()
-
-// 选择文件
-const uploadFile = ref()
-const openFileInput = () => {
-  if (attachmentList.value.length >= 5) return Snackbar.warning(t('resume.uploadFiveCopies'))
-  uploadFile.value.trigger()
-}
-
-// 上传附件
-const handleUploadResume = async (url, title) => {
-  if (!url || !title) return
-  Snackbar.success(t('common.uploadSucMsg'))
-  await savePersonResumeCv({ title, url })
-  getList()
-}
-
-// 删除
-const handleDelete = ({ id }) => {
-  Confirm(t('common.confirmTitle'), t('resume.deleteAttachment')).then(async () => {
-    await deletePersonResumeCv(id)
-    Snackbar.success(t('common.delMsg'))
-    getList()
-  })
-}
-
-// 下载附件
-const handleDownload = (k) => {
-  getBlob(k.url).then(blob => {
-    saveAs(blob, k.title)
-  })
-}
-</script>
-
-<style scoped lang="scss">
-.radius {
-  border-radius: 8px;
-}
-.title {
-  font-weight: 600;
-  font-size: 17px;
-}
-.accountBox {
-  width: 100%;
-  .accountItem {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    width: 50%;
-    height: 80px;
-    padding: 0 12px;
-    .tip-text {
-      font-size: 13px;
-      color: var(--color-666);
-    }
-    .title-text {
-      font-weight: 600;
-      font-size: 18px;
-      &:hover {
-        color: var(--v-primary-base);
-      }
-    }
-  }
-}
-.resume {
-  width: 100%;
-  .topping {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    width: 50%;
-    height: 90px;
-    padding: 12px;
-    margin-right: 12px;
-    cursor: pointer;
-    &:nth-child(2n) {
-      margin-right: 0;
-    }
-    .tip-text {
-      font-size: 12px;
-      color: var(--color-666);
-    }
-    .title-text {
-      font-weight: 600;
-      &:hover {
-        color: var(--v-primary-base);
-      }
-    }
-  }
-}
-.attachment {
-  padding: 12px;
-  .more-text {
-    font-size: 12px;
-    color: var(--color-666);
-    margin-left: 4px;
-  }
-  .upload--text {
-    float: right;
-    color: var(--v-primary-base);
-    font-size: 12px;
-  }
-  .last-update {
-    font-size: 12px;
-    color: var(--color-666);
-  }
-  .attachment-item {
-    color: #555;
-    font-size: 14px;
-    .file-name {
-      width: 219px;
-    }
-  }
-}
-</style>

+ 103 - 14
src/views/recruit/personal/PersonalCenter/index.vue

@@ -1,21 +1,84 @@
 <template>
-  <div class="default-width d-flex pt-3">
-    <div class="left mr-3">
-      <LeftPage></LeftPage>
-    </div>
-    <div class="right">
-      <RightPage></RightPage>
-    </div>
+  <div class="pt-3 default-width d-flex parent">
+    <v-card class="left">
+      <v-list class="side-box" color="primary">
+        <v-list-subheader class="title mb-3">个人中心</v-list-subheader>
+        <template v-for="(item, index) in list">
+          <template v-if="!item.children.length">
+            <v-list-item
+              :key="`${item.name}_${index}`"
+              active-class="active"
+              color="primary"
+              :href="item.path"
+              :to="item.path"
+              :prepend-icon="item.icon"
+              :title="getCurrentLocaleLang() === 'zh_CN' ? item.title : item.enName"
+            >
+            </v-list-item>
+          </template>
+          <v-list-group
+            v-else
+            color="primary"
+            :key="`${item.path}_${item.title}`"
+            :prepend-icon="item.icon"
+          >
+            <template v-slot:activator="{ props }">
+              <v-list-item v-bind="props" :title="getCurrentLocaleLang() === 'zh_CN' ? item.title : item.enName"></v-list-item>
+            </template>
+            <v-list-item
+              v-for="(val, i) in item.children"
+              :key="i"
+              color="primary"
+              :href="val.path"
+              style="padding-left: 40px;"
+              :to="val.path"
+              :title="getCurrentLocaleLang() === 'zh_CN' ? val.title : val.enName"
+              :value="val.path"
+            ></v-list-item>
+          </v-list-group>
+        </template>
+      </v-list>
+    </v-card>
+
+    <v-card class="ml-3 pa-3 page">
+      <router-view></router-view>
+    </v-card>
   </div>
 </template>
 
 <script setup>
-defineOptions({ name: 'personal-center'})
-
-import LeftPage from './dynamic/left.vue'
-import RightPage from './dynamic/right.vue'
+defineOptions({ name: 'person-center'})
+import { computed } from 'vue'
+import { getCurrentLocaleLang } from '@/utils/lang.js'
 import { useUserStore } from '@/store/user'
+import personCenterRoute from '@/router/modules/components/recruit/personCenter'
+
+// 左侧菜单列表
+const list = computed(() => {
+  return getList(personCenterRoute[0].children[0].children)
+})
 
+const getList = (arr, obj = []) => {
+  arr.forEach(element => {
+    if (element.show) return
+    let data = {}
+    data = {
+      title: element?.meta?.title,
+      enName: element?.meta?.enName,
+      icon: element?.meta?.icon,
+      name: element?.name,
+      path: element?.path,
+      children: []
+    }
+    if (element?.children) {
+      getList(element.children, data.children)
+    }
+    obj.push(data)
+  })
+  return obj
+}
+
+// 更新账户信息
 const store = useUserStore()
 const updateAccountInfo = async () => {
   await store.getUserAccountInfo()
@@ -24,10 +87,36 @@ updateAccountInfo()
 </script>
 
 <style scoped lang="scss">
-.left {
-  width: 800px;
+.parent {
+  height: calc(100vh - 150px);
+  overflow-y: auto;
 }
-.right {
+.page {
   flex: 1;
+  width: 100%;
+  overflow-y: auto;
+  overflow-x: hidden;
+}
+.left {
+  position: sticky;
+  width: 250px;
+  height: calc(100vh - 173px);
+}
+.title {
+  color: var(--color-333);
+  font-weight: 600;
+  font-size: 20px;
+}
+::-webkit-scrollbar {
+  width: 6px;
+  height: 10px;
+}
+::-webkit-scrollbar-thumb, .temporaryAdd ::-webkit-scrollbar-thumb, .details_edit ::-webkit-scrollbar-thumb {
+  // 滚动条-颜色
+  background: #c3c3c379;
+}
+::-webkit-scrollbar-track, .temporaryAdd ::-webkit-scrollbar-track, .details_edit ::-webkit-scrollbar-track {
+  // 滚动条-底色
+  background: #e5e5e58f;
 }
 </style>

+ 0 - 0
src/views/recruit/personal/PersonalCenter/components/companyCollection.vue → src/views/recruit/personal/PersonalCenter/jobFeedback/components/companyCollection.vue


+ 0 - 0
src/views/recruit/personal/PersonalCenter/components/delivery.vue → src/views/recruit/personal/PersonalCenter/jobFeedback/components/delivery.vue


+ 0 - 0
src/views/recruit/personal/PersonalCenter/components/interested.vue → src/views/recruit/personal/PersonalCenter/jobFeedback/components/interested.vue


+ 0 - 0
src/views/recruit/personal/PersonalCenter/components/interview/index.vue → src/views/recruit/personal/PersonalCenter/jobFeedback/components/interview/index.vue


+ 4 - 3
src/views/recruit/personal/PersonalCenter/components/interview/item.vue → src/views/recruit/personal/PersonalCenter/jobFeedback/components/interview/item.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="position-item mb-3 job-closed" v-for="(val, i) in props.items" :key="i" @mouseenter="val.active = true" @mouseleave="val.active = false">
+  <div class="position-item mb-3 job-closed elevation-2" v-for="(val, i) in props.items" :key="i" @mouseenter="val.active = true" @mouseleave="val.active = false">
       <div class="info-header">
         <div v-if="val.active && val.status === '0'" class="header-btn">
           <v-btn color="primary" size="small" @click="handleAgree(val)">同意</v-btn>
@@ -26,7 +26,8 @@
         <div class="job-info color-666">
           <div class="job-name ellipsis" style="max-width: 410px;">
             <span class="mr-3 cursor-pointer position-name" @click="handleToPositionDetails(val)">{{ val.job.name }}</span>
-            <span>{{ val.job.payFrom }}-{{ val.job.payTo }}/{{ val.job.payName }}</span>
+            <span v-if="!val.job.payFrom && !val.job.payTo">面议</span>
+            <span v-else>{{ val.job.payFrom ? val.job.payFrom + '-' : '' }}{{ val.job.payTo }}{{ val.job.payName ? '/' + val.job.payName : '' }}</span>
           </div>
           <div class="job-other d-flex align-center">
             <div style="width: 30px;height: 30px;">
@@ -119,7 +120,7 @@ const handleRefuse = (val) => {
   background-color: #fff;
   border-radius: 12px;
   &:hover {
-    box-shadow: 0 16px 40px 0 rgba(153, 153, 153, .3);
+    box-shadow: 0px 3px 5px -1px var(--v-shadow-key-umbra-opacity, rgba(0, 0, 0, 0.2)), 0px 5px 8px 0px var(--v-shadow-key-penumbra-opacity, rgba(0, 0, 0, 0.14)), 0px 1px 14px 0px var(--v-shadow-key-ambient-opacity, rgba(0, 0, 0, 0.12)) !important;
   }
   .info-header {
     height: 48px;

+ 0 - 0
src/views/recruit/personal/PersonalCenter/components/positionCollection.vue → src/views/recruit/personal/PersonalCenter/jobFeedback/components/positionCollection.vue


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

@@ -1,7 +1,7 @@
 <template>
   <div>
     <div v-if="items.length" class="mt-3">
-      <div class="positionItem" v-for="(item, index) in items" :key="index" @mouseenter="item.active = true" @mouseleave="item.active = false">
+      <div class="positionItem elevation-2" v-for="(item, index) in items" :key="index" @mouseenter="item.active = true" @mouseleave="item.active = false">
         <div class="position-and-company">
           <div class="position">
             <div class="float-left">
@@ -116,7 +116,7 @@ const handleEnterprise = (item) => {
   transition: all .2s linear;
   background-color: #fff;
   &:hover {
-    box-shadow: 0 16px 40px 0 rgba(153, 153, 153, .3);
+    box-shadow: 0px 3px 5px -1px var(--v-shadow-key-umbra-opacity, rgba(0, 0, 0, 0.2)), 0px 5px 8px 0px var(--v-shadow-key-penumbra-opacity, rgba(0, 0, 0, 0.14)), 0px 1px 14px 0px var(--v-shadow-key-ambient-opacity, rgba(0, 0, 0, 0.12)) !important;
   }
   .position-and-company {
     display: flex;

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

@@ -0,0 +1,34 @@
+<template>
+  <div>
+    <v-tabs v-model="tab" align-tabs="start" color="primary" bg-color="#f7f8fa">
+      <v-tab v-for="(val, i) in list" :key="i" :value="i">{{ val.title }}</v-tab>
+    </v-tabs>
+    <div :class="['left-bottom', {'mt-3': list[tab].path !== interview && list[tab].path !== interested}]">
+      <component :is="list[tab].path"></component>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'person-center-job-feedback' })
+import { ref } from 'vue'
+import { useI18n } from '@/hooks/web/useI18n'
+import delivery from './components/delivery.vue'
+import interview from './components/interview/index.vue'
+import interested from './components/interested.vue'
+import seenMe from './components/seenMe.vue'
+
+const { t } = useI18n()
+const list = [
+  { title: t('position.delivered'), path: delivery },
+  { title: t('position.interview'), path: interview },
+  { title: t('position.interested'), path: interested },
+  { title: t('position.haveSeenMe'), path: seenMe }
+]
+
+const tab = ref(0)
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 30 - 0
src/views/recruit/personal/PersonalCenter/taskCenter/components/daily.vue

@@ -0,0 +1,30 @@
+<!-- 每日任务 -->
+<template>
+  <div>
+    <div class="resume-title my-5">{{ $t('taskCenter.dailyTask') }}</div>
+    <div v-for="(item, index) in list" :key="'suggest' + index">
+      <div class="d-flex ml-2 my-5">
+        <div class="ellipsis" style="width: 50%;">{{ item.title }}</div>
+        <div class="ellipsis d-flex" style="width: calc(50% - 88px);">
+          <svg-icon class="mx-1" name="integral" size="24"></svg-icon>
+          <span style="color: #10897bad;">{{ item.奖励积分数 }}</span>
+        </div>
+        <v-chip label :color="item.状态 ? 'success' : 'error'">{{ item.状态 ? '已完成' : '未完成' }}</v-chip>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({name: 'personal-taskCenter-daily'})
+const list = [
+  { title: '收藏5个职位', 奖励积分数: '5', 状态: false },
+  { title: '收藏5个公司', 奖励积分数: '5', 状态: true },
+  { title: '投递简历10次', 奖励积分数: '10', 状态: false },
+  { title: '主动沟通10次', 奖励积分数: '10', 状态: false },
+  { title: '分享职位5次', 奖励积分数: '5', 状态: true },
+  { title: '推荐入职成功', 奖励积分数: '50', 状态: false },
+]
+</script>
+<style lang="scss" scoped>
+</style>

+ 105 - 0
src/views/recruit/personal/PersonalCenter/taskCenter/components/signIn.vue

@@ -0,0 +1,105 @@
+<!-- 签到 -->
+<template>
+  <div>
+    <integralShow ref="integralRef"></integralShow>
+    <div class="d-flex justify-center mt-10">
+      <div v-for="(item, index) in configList" :key="'signInKey' + index" >
+        <div
+          class="borderRadius-5 mx-5 py-3 px-4"
+          style="text-align: center;"
+          :style="{'background-color': index < continuousDay ? '#10897ba8' : 'var(--color-f2f4f7)'}"
+        >
+          <!-- 图标 -->
+          <div class="mt-1">
+            <svg-icon v-if="index < continuousDay" name="tick" size="36"></svg-icon>
+            <svg-icon v-else name="integral" size="38"></svg-icon>
+          </div>
+          <div class="mt-2" :style="{'color': index < continuousDay ? '#fff' : 'var(--color-333)'}">
+            <span class="font-12">+</span>
+            <span>{{ item.point }}</span>
+          </div>
+        </div>
+        <div class="mt-2 font-13" style="text-align: center;" :style="{'color': index < continuousDay ? '#10897b' : 'var(--color-333)'}">
+          {{ `${index === todayNumber ? '今天' : '第' + item.day + '天'}` }}
+        </div>
+      </div>
+    </div>
+    <div class="mt-8" style="text-align: right;">
+      <span v-if="continuousDay > 0" class="font-13 color-777 mr-3">
+        您已经连续签到
+        <span class="mx-1" style="color: var(--v-primary-base); font-weight: 600;"> {{ continuousDay }} </span>
+        天,明天再来吧
+      </span>
+      <v-btn class="half-button" color="primary" size="small" :loading="signLoading" :disabled="todaySignIn" @click="handleSignIn">{{ todaySignIn ? '已签到' : '签到' }}</v-btn>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({name: 'personal-taskCenter-signIn'})
+import { ref } from 'vue'
+import { useUserStore } from '@/store/user'
+import { useI18n } from '@/hooks/web/useI18n'
+import { getRewardSignInRecordSummary, getRewardSignInConfigList, createRewardSignInRecord } from '@/api/sign'
+import integralShow from '@/views/integral/pointsManagement/components/integralShow.vue'
+import Snackbar from '@/plugins/snackbar'
+
+const { t } = useI18n()
+const userStore = useUserStore()
+const integralRef = ref()
+// 连续签到天数
+const continuousDay = ref(0)
+// 今天有无签到
+const todaySignIn = ref(false)
+// 规则列表
+const configList = ref([])
+const todayNumber = ref()
+const signLoading = ref(false)
+
+// 获取签到规则列表
+const getConfigList = async () => {
+  const data = await getRewardSignInConfigList()
+  configList.value = data
+}
+getConfigList()
+
+// 获取个人签到统计
+const getSummary = async () => {
+  const data = await getRewardSignInRecordSummary()
+  if (!data) return
+  continuousDay.value = data.continuousDay // 连续签到第n天
+  todaySignIn.value = data.todaySignIn // 今天有无签到
+  todayNumber.value = todaySignIn.value ? continuousDay.value - 1 : continuousDay.value
+}
+getSummary()
+
+// 签到
+const handleSignIn = async () => {
+  signLoading.value = true
+  await createRewardSignInRecord()
+  setTimeout(async () => {
+    await getSummary()
+    Snackbar.success(t('taskCenter.signInSuccess'))
+    signLoading.value = false
+
+    // 更新积分数
+    await userStore.getUserAccountInfo()
+  }, 1000)
+}
+</script>
+
+<style lang="scss" scoped>
+.color-333 { color: var(--color-333); }
+.color-fff { color: #fff; }
+.color-777 { color: var(--color-666); }
+.font-12 { font-size: 12px; }
+.font-13 { font-size: 13px; }
+.font-14 { font-size: 14; }
+.font-16 { font-size: 16px; }
+.borderRadius-5 { border-radius: 5px; }
+.signInRecord {
+  font-size: 14px;
+  color: var(--v-primary-base);
+  cursor: pointer;
+}
+</style>

+ 30 - 0
src/views/recruit/personal/PersonalCenter/taskCenter/components/suggest.vue

@@ -0,0 +1,30 @@
+<!-- 推荐任务 -->
+<template>
+  <div>
+    <div class="resume-title my-5">{{ $t('taskCenter.suggestTask') }}</div>
+    <div v-for="(item, index) in list" :key="'suggest' + index">
+      <div class="d-flex ml-2 my-5">
+        <div class="ellipsis" style="width: 50%;">{{ item.title }}</div>
+        <div class="ellipsis d-flex" style="width: calc(50% - 88px);">
+          <svg-icon class="mx-1" name="integral" size="24"></svg-icon>
+          <span style="color: #10897bad;">{{ item.奖励积分数 }}</span>
+        </div>
+        <v-chip label :color="item.状态 ? 'success' : 'error'">{{ item.状态 ? '已完成' : '未完成' }}</v-chip>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({name: 'personal-taskCenter-suggest'})
+const list = [
+  { title: '首次登录奖励', 奖励积分数: '5', 状态: true },
+  { title: '上传个人头像', 奖励积分数: '10', 状态: false },
+  { title: '完成工作经历', 奖励积分数: '5', 状态: true },
+  { title: '首次投递简历并主动沟通', 奖励积分数: '5', 状态: true },
+  { title: '个人简历被企业接收并回复', 奖励积分数: '20', 状态: false },
+  { title: '购买会员服务费', 奖励积分数: '20', 状态: false },
+]
+</script>
+<style lang="scss" scoped>
+</style>

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

@@ -0,0 +1,21 @@
+<!-- 任务中心 -->
+<template>
+  <div class="py-3">
+    <!-- 签到 -->
+    <signIn class="white-bgc borderRadius5 mb-3 pa-3"></signIn>
+    <!-- 推荐任务 -->
+    <suggest class="white-bgc borderRadius5 mb-3 pa-3"></suggest>
+    <!-- 每日任务 -->
+    <daily class="white-bgc borderRadius5 mb-3 pa-3"></daily>
+  </div>
+</template>
+
+<script setup>
+import signIn from './components/signIn.vue'
+import suggest from './components/suggest.vue'
+import daily from './components/daily.vue'
+defineOptions({name: 'personal-taskCenter-index'})
+</script>
+<style lang="scss" scoped>
+.borderRadius5 { border-radius: 5px; }
+</style>

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

@@ -0,0 +1,33 @@
+<template>
+  <div>
+    <div class="mb-3 pa-3">
+      <v-tabs v-model="tab" align-tabs="start" color="primary" bg-color="#f7f8fa">
+        <v-tab :value="0">{{ $t('enterprise.account.accountBalances') }}</v-tab>
+        <v-tab :value="1">{{ $t('resume.goldCoins') }}</v-tab>
+      </v-tabs>
+    </div>
+    <myBalance v-if="tab === 0"></myBalance>
+    <IntegralPage v-if="tab === 1" :type="1"></IntegralPage>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'person-center-wallet'})
+import myBalance from './myBalance'
+import IntegralPage from '@/views/integral/pointsManagement'
+import { useUserStore } from '@/store/user'
+import { ref } from 'vue'
+
+const tab = ref(0)
+
+const store = useUserStore()
+const updateAccountInfo = async () => {
+  await store.getUserAccountInfo()
+}
+updateAccountInfo()
+
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 120 - 0
src/views/recruit/personal/PersonalCenter/wallet/myBalance/index.vue

@@ -0,0 +1,120 @@
+<template>
+  <div>
+    <div class="pa-3 mb-2 white-bgc">
+      <!-- 余额展示 -->
+      <div class="statisticsBox">
+        <div class="ml-10 mt-2">
+          <div class="item-title">
+            您当前可用余额
+          </div>
+          <div class="item-value">
+            {{ userAccount?.balance && userAccount?.balance > 0 ? (userAccount?.balance / 100.0).toFixed(2) : 0 }}
+            <span
+              class="text-decoration-underline cursor-pointer"
+              style="color: #666; font-size: 13px;"
+              @click="handleRecharge"
+            >充值</span>
+          </div>
+        </div>
+      </div>
+    </div>
+    <!-- 表格 -->
+    <div class="mt-3 white-bgc pa-3 pt-3">
+      <v-tabs v-model="tab" align-tabs="start" color="primary" bg-color="#f7f8fa" @update:model-value="handleChangeTab">
+        <v-tab value="rechargeDetails">{{ $t('points.rechargeDetails') }}</v-tab>
+        <!-- <v-tab :value="otherHeader">otherHeader</v-tab> -->
+      </v-tabs>
+      <CtTable
+        class="mt-3"
+        :items="dataList"
+        :headers="headerList[tab]"
+        :loading="false"
+        :elevation="0"
+        :isTools="false"
+        :showPage="true"
+        :total="total"
+        :page-info="query"
+        itemKey="id"
+        @pageHandleChange="handleChangePage"
+      >
+      </CtTable>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({name: 'myWallet-myBalance-index'})
+import { ref } from 'vue'
+import { timesTampChange } from '@/utils/date'
+import { FenYuanTransform } from '@/utils/position'
+import { getUserWalletRechargePage } from '@/api/recruit/personal/myWallet.js'
+
+const tab = ref('rechargeDetails')
+
+const userAccount = ref(JSON.parse(localStorage.getItem('userAccount')) || {}) // 账户信息
+
+const total = ref(0)
+const query = ref({
+  pageNo: 1,
+  pageSize: 10,
+})
+const dataList = ref([])
+
+const headerList = {
+  rechargeDetails: [
+    { title: '充值金额', key: 'payPrice', value: item =>  FenYuanTransform(item.payPrice) + '元', sortable: false },
+    { title: '支付方式', key: 'payChannelName', sortable: false },
+    { title: '支付订单编号', key: 'payOrderId', sortable: false },
+    { title: '交易时间', key: 'payTime', value: item =>  timesTampChange(item.payTime), sortable: false },
+  ],
+  // otherHeader: []
+}
+
+
+// 积分、签到明细
+const getData = async () => {
+  const res = await getUserWalletRechargePage(query.value)
+  dataList.value = res.list
+  total.value = res.total
+}
+getData()
+
+const handleChangePage = (e) => {
+  query.value.pageNo = e
+  getData()
+}
+
+// 切换
+const handleChangeTab = () => {
+  query.value.pageNo = 1
+  getData()
+}
+
+// 充值
+import { useRouter } from 'vue-router'; const router = useRouter()
+const handleRecharge = () => {
+  router.push({ path: '/personalRecharge' })
+}
+</script>
+
+<style lang="scss" scoped>
+.statisticsBox {
+  padding: 10px 0;
+  border-radius: 10px;
+  background-color: var(--default-bgc);
+  // background-color: var(--color-f3);
+  // font-family: 宋体, SimSun;
+}
+.item-title {
+  font-size: 20px; 
+  color: var(--color-333); 
+  line-height: 28px; 
+  font-weight: bold;
+}
+.item-value {
+  font-size: 42px; 
+  color: #10897bba;
+  line-height: 50px;
+}
+</style>
+

+ 0 - 141
src/views/recruit/personal/accountSettings/dynamic/editPassword.vue

@@ -1,141 +0,0 @@
-<template>
-  <div>
-    <h3>{{ $t('setting.editPassword') }}</h3>
-    <v-divider class="mb-4"></v-divider>
-    <div v-if="!showEdit" class="login-user mb-4">
-      当前登录账号: 
-      <span>{{ userInfo.phone }}</span>
-      <span class="color-primary ml-5 text-decoration-underline cursor-pointer" @click="showEdit = true">修改密码</span>
-    </div>
-    <v-stepper v-if="showEdit" v-model="stepper" color="primary">
-      <v-stepper-header>
-        <v-stepper-item title="手机验证" value="1"></v-stepper-item>
-        <v-divider></v-divider>
-        <v-stepper-item title="修改密码" value="2"></v-stepper-item>
-      </v-stepper-header>
-      <v-stepper-window>
-        <v-stepper-window-item value="1">
-          <div class="d-flex justify-center">
-            <PhonePage ref="phoneRef" style="width: 300px;" :phone="userInfo.phone" :phoneDisabled="true"></PhonePage>
-          </div>
-          <div class="text-center mt-5">
-            <v-btn class="buttons" color="primary" @click="handleValidate">立即验证</v-btn>
-          </div>
-          <div class="text-end">
-            <v-btn color="primary" variant="outlined" @click="handleClose">取 消</v-btn>
-          </div>
-        </v-stepper-window-item>
-        <v-stepper-window-item value="2">
-          <div class="d-flex justify-center">
-            <v-form ref="passwordRef" style="width: 300px;">
-              <v-text-field
-                v-model="query.password"
-                placeholder="请输入密码" 
-                variant="outlined" 
-                density="compact"
-                color="primary"
-                prepend-inner-icon="mdi-lock-outline" 
-                :append-inner-icon="passwordType ? 'mdi-eye-outline' : 'mdi-eye-off-outline'"
-                :type="passwordType ? 'text' : 'password'"
-                :rules="[v=> !!v || '请填写密码', validPassword]"
-                @click:append-inner="passwordType = !passwordType"
-              ></v-text-field>
-              <v-text-field
-                v-model="query.checkPassword"
-                placeholder="请再次输入密码" 
-                variant="outlined" 
-                density="compact"
-                color="primary"
-                prepend-inner-icon="mdi-lock-outline" 
-                :append-inner-icon="show ? 'mdi-eye-outline' : 'mdi-eye-off-outline'"
-                :type="show ? 'text' : 'password'"
-                :rules="[v=> !!v || '请再次输入密码', passwordCheck]"
-                @click:append-inner="show = !show"
-              ></v-text-field>
-            </v-form>
-          </div>
-          <div class="text-center mt-5">
-            <v-btn class="buttons" color="primary" @click="handleSubmit" :loading="loading">确认密码</v-btn>
-          </div>
-          <div class="text-end">
-            <v-btn color="primary" variant="outlined" @click="handleClose">取 消</v-btn>
-          </div>
-        </v-stepper-window-item>
-      </v-stepper-window>
-    </v-stepper>
-  </div>
-</template>
-
-<script setup name="editPassword">
-import PhonePage from '@/components/VerificationCode'
-import { updatePassword } from '@/api/common/index'
-import { ref, reactive, computed } from 'vue'
-import Snackbar from '@/plugins/snackbar'
-
-const phoneRef = ref()
-const passwordRef = ref()
-const passwordType = ref(false)
-const show = ref(false)
-const loading = ref(false)
-const stepper = ref('1')
-const showEdit = ref(false)
-let query = reactive({
-  password: '',
-  checkPassword: ''
-})
-
-const handleClose = () => {
-  showEdit.value = false
-  query = {
-    password: '',
-    checkPassword: ''
-  }
-  stepper.value = '1'
-  passwordType.value = false
-  loading.value = false
-}
-
-// 密码效验
-const regex = /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]).{4,10}$/
-const validPassword = computed(() => {
-  return regex.test(query.password) || '请输入4-10位数由数字、大小写字母组成的密码'
-})
-const passwordCheck = computed(() => {
-  return (query.checkPassword === query.password && regex.test(query.checkPassword)) || '两次密码输入不一致'
-})
-
-// 当前登录的用户信息
-const userInfo = JSON.parse(localStorage.getItem('userInfo'))
-
-const handleSubmit = async () => {
-  const { valid} = await passwordRef.value.validate()
-  if (!valid) return
-  loading.value = true
-  try {
-    await updatePassword({ password: query.password, code: phoneRef.value.loginData.code })
-    Snackbar.success('修改成功')
-  } finally {
-    loading.value = false
-    showEdit.value = false
-  }
-}
-
-const handleValidate = async () => {
-  const { valid } = await phoneRef.value.phoneForm.validate()
-  if (!valid) return
-  stepper.value = '2'
-}
-</script>
-
-<style lang="scss" scoped>
-h3 {
-  font-size: 20px;
-  text-align: left;
-  font-weight: 600;
-  padding-bottom: 25px;
-}
-.login-user {
-  color: var(--color-666);
-  font-weight: 600;
-}
-</style>

+ 0 - 67
src/views/recruit/personal/accountSettings/index.vue

@@ -1,67 +0,0 @@
-<!-- 账号与安全中心 -->
-<template>
-  <div class="d-flex pa-3 default-width" style="height: 700px;">
-    <v-card class="left">
-      <h3>{{ $t('setting.accountSettings') }}</h3>
-      
-      <v-list>
-        <v-list-item
-          v-for="item in items"
-          :key="item.value"
-          :to="item.path"
-          color="primary"
-        >
-          <v-list-item-title class="list-item">{{ item.title }}</v-list-item-title>
-        </v-list-item>
-      </v-list>
-    </v-card>
-
-    <v-card class="right ml-3" style="padding: 30px 60px">
-      <router-view></router-view>
-    </v-card>
-  </div>
-</template>
-
-<script setup>
-defineOptions({ name:'personal-account-index'})
-import { useI18n } from '@/hooks/web/useI18n'
-const { t } = useI18n()
-
-const items = [
-  { title: t('setting.accountBinding'), path: '/recruit/personal/accountSettings/accountBinding', },
-  { title: t('setting.realNameAuthentication'), path: '/recruit/personal/accountSettings/realAuthentication', },
-  { title: t('setting.editPassword'), path: '/recruit/personal/accountSettings/editPassword', },
-  { title: t('setting.privacyPolicySettings'), path: '/recruit/personal/accountSettings/privacySettings' }
-]
-</script>
-
-<style lang="scss" scoped>
-.left {
-  width: 220px;
-}
-.right {
-  flex: 1;
-}
-:deep(.v-list-item) {
-  padding: 0 !important;
-}
-.list-item {
-  height: 62px;
-  width: 100%;
-  line-height: 62px;
-  font-weight: 500;
-  // color: var(--color-666);
-  font-size: 16px;
-  cursor: pointer;
-  margin: 0;
-  padding-left: 40px;
-  text-align: left;
-  transition: all .3s;
-}
-h3 {
-  font-size: 20px;
-  padding: 30px 0 30px 40px;
-  text-align: left;
-  font-weight: 600;
-}
-</style>

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

@@ -1,7 +1,7 @@
 <template>
   <div class="default-width">
     <div class="white-bgc py-3">
-      <headSearch placeholder="搜索公司" @handleSearch="val => handleSearch(val, 'name')"></headSearch>
+      <headSearch placeholder="搜索公司" tipsText="公司关键字搜索" @handleSearch="val => handleSearch(val, 'name')"></headSearch>
       <div class="px-5 mt-3 clear-parent">
         <areaType :isClear="clear" @handleClick="handleSearch"></areaType>
         <!-- <industryType :isClear="clear" @handleClick="handleSearch"></industryType> -->

+ 5 - 3
src/views/recruit/personal/home/components/hotJobs.vue

@@ -1,7 +1,9 @@
 <template>
-  <div class="default-width text-center mb-6">
-    <span class="mr-2 color-666">{{ $t('position.popularPosition') }}:</span>
-    <span v-for="(item, index) in jobs" :key="index" label size="small" class="ml-2 my-1 tag"  @click="handleClick(item)">{{ item.nameCn }}</span>
+  <div class="default-width text-center mb-6 d-flex align-center">
+    <span class="mr-2 color-666" style="width: 100px;">{{ $t('position.popularPosition') }}:</span>
+    <div style="flex: 1; overflow: hidden; height: 40px; ">
+      <span v-for="(item, index) in jobs" :key="index" label size="small" class="ml-2 my-1 tag"  @click="handleClick(item)">{{ item.nameCn }}</span>
+    </div>
   </div>
 </template>
 

+ 2 - 2
src/views/recruit/personal/home/components/hotPromotedPositions.vue

@@ -25,7 +25,7 @@
         <Empty v-else class="mb-3" :elevation="false"></Empty>
       </v-window-item>
     </v-window>
-    <div class="text-center">
+    <div class="text-center mt-5" style="border-top: 1px solid #ccc; padding-top: 30px;">
       <v-btn class="buttons btnColor" to="/recruit/personal/position">{{ $t('position.moreBtn') }}</v-btn>
     </div>
   </div>
@@ -43,7 +43,7 @@ const items = ref([])
 // 推荐职位
 const getPositionList = async () => {
   const api = tab.value === 1 ? getPromotedPosition : (tab.value === 2 ? getLatestPosition : getHirePosition)
-  const { list } = await api({ pageNo: 1, pageSize: 8 })
+  const { list } = await api({ pageNo: 1, pageSize: 9 })
   items.value = dealDictArrayData([], list)
 }
 getPositionList()

+ 1 - 1
src/views/recruit/personal/home/components/popularEnterprises.vue

@@ -27,7 +27,7 @@ const getHotEnterpriseList = async () => {
   items.value = list.map(e => {
     let jobList = []
     const enterprise = dealDictObjData({}, e.enterprise)
-    if (e.jobList && e.jobList.length) jobList = dealDictArrayData([], e.jobList).slice(0, 3)
+    if (e.jobList && e.jobList.length) jobList = dealDictArrayData([], e.jobList).slice(0, 2)
     return { enterprise, jobList, active: false }
   })
 }

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác