Browse Source

职位卡片

Xiao_123 3 months ago
parent
commit
2cfda00e8b

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

@@ -1,10 +1,13 @@
 <!-- 搜索页面的职位详情-长条 -->
 <template>
-  <div class="positionItem" v-for="(item, index) in list" :key="index" @mouseenter="item.active = true" @mouseleave="item.active = false">
+  <div class="positionItem px-3 pt-3" v-for="(item, index) in list" :key="index" @mouseenter="item.active = true" @mouseleave="item.active = false">
     <div class="position-and-company">
       <!-- 职位 -->
       
-      <div class="position" @mouseenter="item.positionActive = true" @mouseleave="item.positionActive = false" @click.stop="handlePosition(item)">
+      <div
+        class="position cursor-pointer rounded-lg" :class="item.positionActive ? 'elevation-5' : ''"
+        @mouseenter="item.positionActive = true" @mouseleave="item.positionActive = false" @click.stop="handlePosition(item)"
+      >
         <div class="d-flex">
           <div v-if="item?.job?.hire" class="mr-3">
             <svg-icon name="pin" size="45"></svg-icon>
@@ -40,14 +43,16 @@
         </div>
       </div>
       <!-- 公司 -->
-      <div class="company" @click.stop="jumpToEnterpriseDetail(item.enterprise.id, true)">
+      <div
+        class="company rounded-lg pa-4 cursor-pointer" :class="item.companyActive ? 'elevation-5' : ''"
+        @click.stop="jumpToEnterpriseDetail(item.enterprise.id, true)" @mouseenter="item.companyActive = true" @mouseleave="item.companyActive = false">
         <div class="float-left">
           <v-img :src="item?.enterprise.logoUrl || 'https://minio.citupro.com/dev/menduner/company-avatar.png'" :alt="item.enterprise.anotherName" :width="40" style="height: 40px;border-radius: 4px;"/>
         </div>
         <div class="company-info">
           <v-hover>
             <template v-slot:default="{ isHovering, props }">
-              <h3 v-bind="props" v-ellipse-tooltip :class="{'default-active': isHovering }" class="title1">{{ formatName(item.enterprise.anotherName || item.enterprise.name) }}</h3>
+              <h3 v-bind="props" v-ellipse-tooltip :class="{'default-active': isHovering }" class="title1" style="max-width: 495px;">{{ formatName(item.enterprise.anotherName || item.enterprise.name) }}</h3>
             </template>
           </v-hover>
           <p v-ellipse-tooltip class="mt-2" style="max-width: 420px;">
@@ -59,16 +64,16 @@
       </div>
     </div>
     <!-- 底部 -->
-    <div class="footer">
+    <div class="footer mt-1">
       <div class="footer-left">
         <template v-for="(jobTag, jobTagsIndex) in item.job.tagList" :key="jobTagsIndex">
-          <span class="textColor666 mx-1" v-if="jobTagsIndex">|</span>
-          <span class="textColor666 dis">{{ jobTag }}</span>
+          <span class="color-666 mx-1" v-if="jobTagsIndex">|</span>
+          <span class="color-666 dis">{{ jobTag }}</span>
         </template>
       </div>
       <div class="footer-right" v-if="item.contact">
         <v-avatar size="x-small" :image="getUserAvatar(item.contact?.avatar, item.contact?.sex)"></v-avatar>
-        <span class="mx-2 textColor666">
+        <span class="mx-2 color-666">
           {{ item.contact?.name }}
           <span v-if="item?.contact?.postNameCn">|</span>
           {{ item.contact?.postNameCn }}
@@ -137,35 +142,29 @@ const handlePosition = (item) => {
 .company-info {
   float: left;
   margin-left: 16px;
-  // width: 282px;
 }
 .company-info p {
   height: 18px;
-  font-size: 13px;
+  font-size: 14px;
   font-weight: 400;
   color: var(--color-999);
 }
-.textColor666 { color: var(--color-666); }
 .positionItem {
-  // width: 850px;
   width: 100%;
   margin-bottom: 12px;
   border-radius: 12px;
   padding: 0;
   overflow: hidden;
-  cursor: pointer;
   transition: all .2s linear;
   background-color: #fff;
-  &:hover {
-    box-shadow: 0 16px 40px 0 rgba(153, 153, 153, .3);
-  }
+  box-shadow: 0 2px 20px 0 rgba(37, 39, 48, .2);
   .position-and-company {
     display: flex;
-    padding: 16px 20px;
     width: 100%;
     .position {
       width: 500px;
-      padding-right: 30px;
+      padding: 15px 20px 16px 20px;
+      margin-right: 30px;
     }
   }
   .footer {
@@ -189,11 +188,6 @@ const handlePosition = (item) => {
       white-space: nowrap;
     }
   }
-  .ellipsisStyle {
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-  }
 }
 .salary {
   font-size: 16px;
@@ -202,4 +196,4 @@ const handlePosition = (item) => {
   line-height: 22px;
   flex: none;
 }
-</style>
+</style>

+ 3 - 2
src/components/headSearch/index.vue

@@ -37,8 +37,9 @@
         @click:clear="handleSearch"
         @keyup.enter="handleSearch"
       ></v-text-field>
-      <!-- <div class="searchBtn" @click="handleSearch">搜索</div> -->
-      <v-btn class="searchBtn" @click="handleSearch">搜索</v-btn>
+      <v-hover v-slot="{ isHovering, props }">
+        <v-btn v-bind="props" v-ripple.center class="searchBtn" @click="handleSearch" :class="isHovering ? 'elevation-10' : 'elevation-5'">搜索</v-btn>
+      </v-hover>
     </div>
   </div>
 </template>

+ 7 - 20
src/styles/recruit/company.css

@@ -33,27 +33,23 @@
 
 .sub-li {
   position: relative;
-  width: calc((100% - 36px) / 4);
-  min-width: calc((100% - 36px) / 4);
-  max-width: calc((100% - 36px) / 4);
+  width: calc((100% - 24px) / 3);
+  min-width: calc((100% - 24px) / 3);
+  max-width: calc((100% - 24px) / 3);
   margin: 0 12px 12px 0;
-  height: 160px;
+  height: 183px;
   border-radius: 12px;
   padding: 0;
   overflow: hidden;
   transition: all .2s linear;
   background-color: #fff;
-  cursor: pointer;
+  box-shadow: 0 2px 20px 0 rgba(37, 39, 48, 0.2);
 }
 
-.sub-li:nth-child(4n) {
+.sub-li:nth-child(3n) {
   margin-right: 0;
 }
 
-.sub-li:hover {
-  box-shadow: 0 16px 40px 0 rgba(153, 153, 153, 0.3);
-}
-
 .company-info {
   float: left;
   margin-left: 16px;
@@ -62,9 +58,8 @@
 
 .company-info-top {
   display: flex;
-  height: 90px;
+  height: 70px;
   line-height: 90px;
-  padding: 0 20px;
   align-items: center;
   overflow: hidden;
 }
@@ -92,15 +87,12 @@
 }
 
 .company-info-bottom {
-  width: 100%;
   height: 70px;
-  padding: 10px 15px;
 }
 
 .name {
   position: relative;
   line-height: 22px;
-  color: #404040;
   margin-right: 8px;
   overflow: hidden;
   text-overflow: ellipsis;
@@ -117,8 +109,3 @@
   text-align: right;
   flex: 1;
 }
-
-.job-hover:hover {
-  color: var(--v-primary-base);
-  background-color: #f2f4f7;
-}

+ 1 - 1
src/styles/recruit/company.min.css

@@ -1 +1 @@
-.label-title{width:64px;font-weight:500;margin-right:24px;color:var(--color-222)}.label-content{flex:1}.label-color{color:var(--color-222);font-size:14px;margin-right:24px;display:inline-block;cursor:pointer}.label-color:hover{color:var(--v-primary-base)}.actives{color:var(--v-primary-base);font-weight:600}.company-box{display:flex;flex-wrap:wrap}.sub-li{position:relative;width:calc((100% - 36px) / 4);min-width:calc((100% - 36px) / 4);max-width:calc((100% - 36px) / 4);margin:0 12px 12px 0;height:160px;border-radius:12px;padding:0;overflow:hidden;transition:all .2s linear;background-color:#fff;cursor:pointer}.sub-li:nth-child(4n){margin-right:0}.sub-li:hover{box-shadow:0 16px 40px 0 rgba(153,153,153,0.3)}.company-info{float:left;margin-left:16px;width:282px}.company-info-top{display:flex;height:90px;line-height:90px;padding:0 20px;align-items:center;overflow:hidden}.company-info h3{height:22px;font-size:18px;font-weight:700;color:var(--color-333);line-height:22px;margin:0 0 4px 0;padding:0;max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.company-info p{height:18px;font-size:13px;font-weight:400;color:var(--color-999);line-height:18px}.company-info-bottom{width:100%;height:70px;padding:10px 15px}.name{position:relative;line-height:22px;color:#404040;margin-right:8px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:all linear .2s}.salary{font-size:16px;float:right;color:var(--v-error-base);line-height:22px;max-width:none;text-align:right;flex:1}.job-hover:hover{color:var(--v-primary-base);background-color:#f2f4f7}
+.label-title{width:64px;font-weight:500;margin-right:24px;color:var(--color-222)}.label-content{flex:1}.label-color{color:var(--color-222);font-size:14px;margin-right:24px;display:inline-block;cursor:pointer}.label-color:hover{color:var(--v-primary-base)}.actives{color:var(--v-primary-base);font-weight:600}.company-box{display:flex;flex-wrap:wrap}.sub-li{position:relative;width:calc((100% - 24px) / 3);min-width:calc((100% - 24px) / 3);max-width:calc((100% - 24px) / 3);margin:0 12px 12px 0;height:183px;border-radius:12px;padding:0;overflow:hidden;transition:all .2s linear;background-color:#fff;box-shadow:0 2px 20px 0 rgba(37,39,48,0.2)}.sub-li:nth-child(3n){margin-right:0}.company-info{float:left;margin-left:16px;width:282px}.company-info-top{display:flex;height:70px;line-height:90px;align-items:center;overflow:hidden}.company-info h3{height:22px;font-size:18px;font-weight:700;color:var(--color-333);line-height:22px;margin:0 0 4px 0;padding:0;max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.company-info p{height:18px;font-size:13px;font-weight:400;color:var(--color-999);line-height:18px}.company-info-bottom{height:70px}.name{position:relative;line-height:22px;margin-right:8px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:all linear .2s}.salary{font-size:16px;float:right;color:var(--v-error-base);line-height:22px;max-width:none;text-align:right;flex:1}

+ 7 - 18
src/styles/recruit/company.scss

@@ -32,23 +32,20 @@
 
 .sub-li {
   position: relative;
-  width: calc((100% - 36px) / 4);
-  min-width: calc((100% - 36px) / 4);
-  max-width: calc((100% - 36px) / 4);
+  width: calc((100% - 24px) / 3);
+  min-width: calc((100% - 24px) / 3);
+  max-width: calc((100% - 24px) / 3);
   margin: 0 12px 12px 0;
-  height: 160px;
+  height: 183px;
   border-radius: 12px;
   padding: 0;
   overflow: hidden;
   transition: all .2s linear;
   background-color: #fff;
-  cursor: pointer;
-  &:nth-child(4n) {
+  box-shadow: 0 2px 20px 0 rgba(37, 39, 48, .2);
+  &:nth-child(3n) {
     margin-right: 0;
   }
-  &:hover {
-    box-shadow: 0 16px 40px 0 rgba(153, 153, 153, .3);
-  }
 }
 .company-info {
   float: left;
@@ -57,9 +54,8 @@
 }
 .company-info-top {
   display: flex;
-  height: 90px;
+  height: 70px;
   line-height: 90px;
-  padding: 0 20px;
   align-items: center;
   overflow: hidden;
 }
@@ -84,14 +80,11 @@
   line-height: 18px;
 }
 .company-info-bottom {
-  width: 100%;
   height: 70px;
-  padding: 10px 15px;
 }
 .name {
   position: relative;
   line-height: 22px;
-  color: #404040;
   margin-right: 8px;
   overflow: hidden;
   text-overflow: ellipsis;
@@ -107,7 +100,3 @@
   text-align: right;
   flex: 1;
 }
-.job-hover:hover {
-  color: var(--v-primary-base);
-  background-color: #f2f4f7;
-}

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

@@ -2,7 +2,7 @@
   <div style="position: relative; height: 60vh;">
     <div  v-if="userInfo?.entitlement?.viewersList && userInfo?.vipFlag && userInfo?.vipExpireDate && userInfo?.vipExpireDate > Date.now()">
       <div v-if="items.length" class="mt-3">
-        <div class="positionItem elevation-2" v-for="(item, index) in items" :key="index" @mouseenter="item.active = true" @mouseleave="item.active = false">
+        <div class="positionItem" :class="item.active ? 'elevation-8' : 'elevation-3'" 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">
@@ -129,9 +129,6 @@ const goBuy = () => {
   overflow: hidden;
   transition: all .2s linear;
   background-color: #fff;
-  &:hover {
-    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;
     padding: 16px 20px;

+ 7 - 4
src/views/recruit/personal/company/components/companyItem.vue

@@ -1,12 +1,14 @@
 <template>
   <div class="company-box">
     <div class="sub-li" v-for="item in list" :key="item.enterprise.id">
-      <div class="company-info-top" @click.stop="jumpToEnterpriseDetail(item.enterprise.id, true)" @mouseenter="item.active = true" @mouseleave="item.active = false">
+      <div
+        class="company-info-top ma-3 rounded-lg px-3 cursor-pointer" :class="item.active ? 'elevation-5' : ''"
+        @click.stop="jumpToEnterpriseDetail(item.enterprise.id, true)" @mouseenter="item.active = true" @mouseleave="item.active = false">
         <div class="float-left">
-          <v-img :src="item.enterprise.logoUrl || 'https://minio.citupro.com/dev/menduner/company-avatar.png'" contain rounded :alt="item.enterprise.anotherName" :width="40" :height="40"/>
+          <v-img :src="item.enterprise.logoUrl || 'https://minio.citupro.com/dev/menduner/company-avatar.png'" contain rounded :alt="item.enterprise.anotherName" :width="50" :height="50" />
         </div>
         <div class="company-info">
-          <h3 :class="{'default-active': item.active }">{{ formatName(item.enterprise.anotherName || item.enterprise.name) }}</h3>
+          <h3 v-ellipse-tooltip :class="{'default-active': item.active }">{{ formatName(item.enterprise.anotherName || item.enterprise.name) }}</h3>
           <p>{{ item.enterprise.industryName }}</p>
         </div>
       </div>
@@ -16,8 +18,9 @@
           <div v-if="item?.job && Object.keys(item.job).length" @click.stop="handleClickPosition(item.job)">
             <div class="mb-1 d-flex">
               <p
+                v-ellipse-tooltip
                 class="mr-3 cursor-pointer name"
-                :style="{'max-width': !item.job.payFrom && !item.job.payTo ? '270px' : '200px', 'color': isHovering ? 'var(--v-primary-base)' : '#404040'}"
+                :style="{'max-width': !item.job.payFrom && !item.job.payTo ? '270px' : '180px', 'color': isHovering ? 'var(--v-primary-base)' : '#404040'}"
               >{{ formatName(item.job.name) }}</p>
               <span v-if="!item.job.payFrom && !item.job.payTo" class="salary">面议</span>
               <span v-else class="salary">{{ item.job.payFrom ? item.job.payFrom + '-' : '' }}{{ item.job.payTo }}{{ item.job.payName ? '/' + item.job.payName : '' }}</span>

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

@@ -1,8 +1,8 @@
 <template>
   <div class="default-width">
-    <buttons :current="2" style="position: sticky;"></buttons>
+    <buttons :current="2" style="position: sticky;" class="mx-4"></buttons>
     <div class="company-content">
-      <div class="white-bgc pb-3 pt-5">
+      <div class="white-bgc pb-3 pt-5 mx-4">
         <headSearch v-model="content" placeholder="搜索公司关键字" @handleSearch="val => handleSearch(val, 'name')"></headSearch>
         <div class="px-5 mt-3 clear-parent">
           <!-- <areaType :isClear="clear" @handleClick="handleSearch"></areaType> -->
@@ -11,7 +11,7 @@
           <div class="clear" @click="handleClear">清空筛选条件</div>
         </div>
       </div>
-      <div v-if="items.length">
+      <div v-if="items.length" class="mx-4">
         <companyItem class="mt-3" :list="items"></companyItem>
         <MPagination
           :total="total"
@@ -20,7 +20,7 @@
           @handleChange="handleChangePage"
         ></MPagination>
       </div>
-      <Empty v-else class="mt-3" :message="noParams? '请输入或选择对应的条件查找公司' : '没有对应的公司,请更换搜索条件后再试'"></Empty>
+      <Empty v-else class="mt-3 mx-4" :message="noParams? '请输入或选择对应的条件查找公司' : '没有对应的公司,请更换搜索条件后再试'"></Empty>
     </div>
   </div>
 </template>
@@ -48,7 +48,7 @@ const total = ref(0)
 const items = ref([])
 const pages = ref({
   pageNo: 1,
-  pageSize: 12
+  pageSize: 9
 })
 const query = ref({})
 
@@ -165,4 +165,4 @@ const handleChangePage = (index) => {
   width: 0;
   height: 0;
 }
-</style>
+</style>

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

@@ -11,26 +11,16 @@
       <v-tab :value="2">{{ $t('position.latest') }}</v-tab>
       <v-tab :value="3">{{ $t('position.hire') }}</v-tab>
     </v-tabs>
-    <v-window v-model="tab" class="mt-5">
+    <v-window v-model="tab" class="pt-5 px-2 pb-2">
       <v-window-item v-for="v in 3" :value="v" :key="v">
         <PositionCard v-if="items.filter(Boolean) && items.length" :isOpenWindow="false" :items="items" :tab="tab" @position="handlePosition"></PositionCard>
         <Empty v-else class="mb-3" :elevation="false"></Empty>
       </v-window-item>
-      <!-- <v-window-item :value="1">
-        <PositionCard v-if="items.filter(Boolean) && items.length" :items="items" :tab="tab" @position="handlePosition"></PositionCard>
-        <Empty v-else class="mb-3" :elevation="false"></Empty>
-      </v-window-item>
-      <v-window-item :value="2">
-        <PositionCard v-if="items.filter(Boolean) && items.length" :isOpenWindow="false" :items="items" :tab="tab" @position="handlePosition"></PositionCard>
-        <Empty v-else class="mb-3" :elevation="false"></Empty>
-      </v-window-item>
-      <v-window-item :value="3">
-        <PositionCard v-if="items.filter(Boolean) && items.length" :isOpenWindow="false" :items="items" :tab="tab" @position="handlePosition"></PositionCard>
-        <Empty v-else class="mb-3" :elevation="false"></Empty>
-      </v-window-item> -->
     </v-window>
-    <div class="text-center mt-5" style="border-top: 1px solid #ccc; padding-top: 30px;">
-      <v-btn class="buttons btnColor" @click.stop="handleToMore">{{ $t('position.moreBtn') }}</v-btn>
+    <div class="text-center mt-4" style="border-top: 1px solid #ccc; padding-top: 30px;">
+      <v-hover v-slot="{ isHovering, props }">
+        <v-btn v-bind="props" v-ripple.center class="buttons btnColor" :class="isHovering ? 'elevation-10' : 'elevation-5'" @click.stop="handleToMore">{{ $t('position.moreBtn') }}</v-btn>
+      </v-hover>
     </div>
   </div>
 </template>
@@ -62,7 +52,7 @@ const handlePosition = (item) => {
 }
 
 const handleToMore = () => {
-  window.open('/recruit/personal/position')
+  window.open(tab.value === 1 ? '/recruit/personal/recommend' : '/recruit/personal/position')
 }
 </script>
 
@@ -75,4 +65,4 @@ const handleToMore = () => {
   color: #666;
   font-size: 16px;
 }
-</style>
+</style>

+ 9 - 5
src/views/recruit/personal/home/index.vue

@@ -73,7 +73,11 @@ const preferredContent = ref({})
 const getSystemWebContent = async () => {
   const data = await getWebContent()
   // 优选集团
-  preferred.value = data.pcHomePreferred
+  preferred.value = data.pcHomePreferred ? data.pcHomePreferred.sort((a, b) => {
+    const sortA = a.sort || Infinity
+    const sortB = b.sort || Infinity
+    return sortA - sortB
+  }) : []
   preferredContent.value = data.appPreferredGroup
   // 顶部广告
   topAdvertise.value = data.pcTop && data.pcTop.length ? data.pcTop[0].img : ''
@@ -146,14 +150,14 @@ onMounted(async () => {
 .stickyBox {
   position: sticky;
   top: 48px;
-  z-index: 999;
+  z-index: 998;
   background-color: var(--default-bgc);
 }
 .advertiseBox {
   position: sticky;
-  top: 128px;
+  top: 150px;
   left: 0;
-  z-index: 999;
+  z-index: 998;
   width: 15px;
   max-width: 180px;
   margin-left: -200px;
@@ -212,4 +216,4 @@ onMounted(async () => {
   min-width: 1160px;
   margin: 0 auto;
 }
-</style>
+</style>

+ 38 - 54
src/views/recruit/personal/position/components/details.vue

@@ -8,22 +8,24 @@
         </div>
         <v-btn v-if="showContentRight" color="primary" variant="text" size="large" @click.stop="handleReturn" prepend-icon="mdi-chevron-triple-left">返回上一页</v-btn>
       </div>
-      <div class="text-end">
-        <span v-if="!info.payFrom && !info.payTo" class="salary font-size-20">面议</span>
-        <span v-else class="salary font-size-20">{{ info.payFrom ? info.payFrom + '-' : ''}}{{ info.payTo }}{{ positionInfo.payName ? '/' + positionInfo.payName : '' }}</span>
-      </div>
-      <div class="refresh-time text-end">{{ timesTampChange(info.updateTime) }} {{ $t('common.refresh') }} <v-icon color="primary" size="20">mdi-circle-medium</v-icon></div>
-      <div class="banner-tags mt-4">
-        <span v-for="k in desc" :key="k.mdi">
-          <span v-if="positionInfo[k.value] || k.value === 'areaName'" class="mr-10">
-            <v-icon color="var(--color-666)" size="20">{{ k.mdi }}</v-icon>
-            <span class="ml-1">
-              {{ k.value === 'areaName' ? !positionInfo.areaId ? '全国' : positionInfo.area?.str : positionInfo[k.value] }}
-              <!-- {{ (k.value === 'areaName' && !positionInfo.areaId) ? '全国' : positionInfo[k.value] }} -->
+      <div class="d-flex mt-1 justify-space-between align-center">
+        <div class="banner-tags">
+          <span v-for="k in desc" :key="k.mdi">
+            <span v-if="positionInfo[k.value] || k.value === 'areaName'" class="mr-10">
+              <v-icon color="var(--color-666)" size="20">{{ k.mdi }}</v-icon>
+              <span class="ml-1">
+                {{ k.value === 'areaName' ? !positionInfo.areaId ? '全国' : positionInfo.area?.str : positionInfo[k.value] }}
+                <!-- {{ (k.value === 'areaName' && !positionInfo.areaId) ? '全国' : positionInfo[k.value] }} -->
+              </span>
             </span>
           </span>
-        </span>
+        </div>
+        <div>
+          <span v-if="!info.payFrom && !info.payTo" class="salary font-size-20">面议</span>
+          <span v-else class="salary font-size-20">{{ info.payFrom ? info.payFrom + '-' : ''}}{{ info.payTo }}{{ positionInfo.payName ? '/' + positionInfo.payName : '' }}</span>
+        </div>
       </div>
+      <div class="refresh-time text-end">{{ timesTampChange(info.updateTime) }} {{ $t('common.refresh') }} <v-icon color="primary" size="20">mdi-circle-medium</v-icon></div>
       <div class="banner-tools my-4">
         <v-chip size="small" label v-for="(k, i) in info.tagList" :key="i" class="mr-1" color="primary">{{ k }}</v-chip>
       </div>
@@ -65,7 +67,7 @@
             <v-divider class="my-3"></v-divider>
             <div class="contact" v-if="Object.keys(info).length">
               <div class="float-left d-flex align-center">
-                <v-img :src="getUserAvatar(info.contact.avatar, info.contact.sex)" rounded contain :width="45" style="height: 45px;"></v-img>
+                <v-img :src="getUserAvatar(info.contact.avatar, info.contact.sex)" :width="45" rounded contain style="height: 45px;"></v-img>
                 <div class="ml-2">
                   <div class="contact-name">{{ info.contact.name }}</div>
                   <div class="contact-info">
@@ -126,34 +128,11 @@
     <!-- 选择简历 -->
     <selectResumeDialog v-model="showResume" :list="resumeList" @submit="handleSubmit" @close="handleClose"></selectResumeDialog>
 
-    <!-- 职位分享 -->
-    <Dialog
-      :visible="shareDialog" :widthType="2" :footer="false" titleClass="text-h6"
-      :title="$t('position.rewardsShared')"
-      @close="shareDialog = false"
-    >
-      <div>
-        <div class="mb-3 text-center">微信分享:保存图片分享给好友</div>
-        <div class="d-flex align-center flex-column">
-          <v-img :src="previewSrc" width="200" height="250"></v-img>
-          <div class="mt-5">
-            <v-btn color="primary" variant="outlined" prepend-icon="mdi-eye-outline" @click="showPreview = true" style="width: 133px">预 览</v-btn>
-            <v-btn class="ml-3" color="primary" variant="outlined" prepend-icon="mdi-arrow-down-bold-box-outline" @click="handleDownloadImage" style="width: 133px">保存到本地</v-btn>
-          </div>
-        </div>
-      </div>
-      <template #footer>
-        <v-divider></v-divider>
-        <div>
-          <v-btn class="float-right ma-2" color="primary" variant="text" @click="shareDialog = false">{{ $t('common.close') }}</v-btn>
-        </div>
-      </template>
-    </Dialog>
-    <PreviewImg v-if="showPreview" :list="[previewSrc]" @close="showPreview = false" @download="handleDownloadImage"   :isImage="true"></PreviewImg>
+    <!-- 图片预览 -->
+    <PreviewImage v-if="showPreview" :urlList="[previewSrc]" :fileName="fileName" @close="showPreview = !showPreview" />
 
     <Loading :visible="loading"></Loading>
     <div v-if="Object.keys(info).length && Object.keys(positionInfo).length" style="position: absolute; left: -9999px; bottom: 0">
-      
       <PosterPage :id="id" :info="info" :positionInfo="positionInfo" ref="share"></PosterPage>
     </div>
 
@@ -164,7 +143,7 @@
 
 <script setup>
 defineOptions({ name: 'position-details' })
-import { ref } from 'vue'
+import { ref, computed } from 'vue'
 import { useRouter } from 'vue-router'
 import Snackbar from '@/plugins/snackbar'
 import html2canvas from 'html2canvas'
@@ -174,7 +153,6 @@ import PosterPage from './poster.vue'
 import selectResumeDialog from './jobDetails/selectResumeDialog'
 import similarPositions from '@/components/Position/similarPositions.vue'
 import EnterpriseInfo from '@/components/Enterprise/info.vue'
-import Dialog from '@/components/CtDialog'
 import loginPage from '@/views/common/loginDialog.vue'
 import {
   getPositionDetails,
@@ -187,7 +165,7 @@ import {
 } from '@/api/position'
 import { getPersonResumeCv, savePersonResumeCv } from '@/api/recruit/personal/resume'
 
-import { downloadBase64, DPR } from '@/utils'
+import { DPR } from '@/utils'
 import { timesTampChange } from '@/utils/date'
 import { dealDictObjData, dealDictArrayData, commissionCalculation } from '@/utils/position'
 import { getToken } from '@/utils/auth'
@@ -196,6 +174,7 @@ import { checkPersonBaseInfo } from '@/utils/check'
 import dialogExtend from '@/plugins/dialogExtend'
 import { formatName } from '@/utils/getText'
 
+const emit = defineEmits(['preview'])
 const props = defineProps({
   defaultWidth: {
     type: Boolean,
@@ -213,6 +192,11 @@ const props = defineProps({
     type: [String, Number],
     default: ''
   },
+  // 是否为推荐职位引用
+  isRecommend: {
+    type: Boolean,
+    default: false
+  }
 })
 
 const { t } = useI18n()
@@ -281,6 +265,13 @@ const cleanedHtml = (text) => {
   return cleaned
 }
 
+// 职位详情分享图片下载文件名
+const fileName = computed(() => {
+  const { name, areaName, payFrom, payTo } = info.value
+  const salary = payFrom && payTo ? `${payFrom ? '_' + payFrom + '-' : ''}${payTo}` : '-面议'
+  return `${name}${areaName ? '_' + areaName : ''}${salary}${positionInfo.value.payName ? '-' + positionInfo.value.payName : ''}`
+})
+
 const share = ref()
 // 生成图片
 const generateAndDownloadImage = async () => {
@@ -291,6 +282,10 @@ const generateAndDownloadImage = async () => {
     const image = canvas.toDataURL().replace(/^data:image\/(png|jpg);base64,/, '')
     previewSrc.value = `data:image/png;base64,${image}`
     loading.value = false
+    if (props.isRecommend) {
+      emit('preview', previewSrc.value, fileName.value)
+      return
+    }
     showPreview.value = true
   } catch (error) {
     console.error('Error generating image:', error)
@@ -298,16 +293,6 @@ const generateAndDownloadImage = async () => {
   }
 }
 
-// 保存图片到本地
-const handleDownloadImage = () => {
-  const { name, areaName, payFrom, payTo } = info.value
-  const salary = payFrom && payTo ? `${payFrom ? '_' + payFrom + '-' : ''}${payTo}` : '-面议'
-  downloadBase64(previewSrc.value, `${name}${areaName ? '_' + areaName : ''}${salary}${positionInfo.value.payName ? '-' + positionInfo.value.payName : ''}`)
-  setTimeout(() => {
-    Snackbar.success('下载成功')
-  }, 500);
-}
-
 // 相似职位
 const similarList = ref([])
 const getSimilarPositionList = async () => {
@@ -352,7 +337,6 @@ const getCollectionStatus = async () => {
 getCollectionStatus()
 
 // 分享有礼
-const shareDialog = ref(false)
 const handleShare = async () => {
   nextFunc.value = handleShare // 登录成功或强制填写个人信息成功后回调
   if (!getToken()) {
@@ -512,4 +496,4 @@ const toDetails = async (info) => {
 
 <style lang="scss" scoped>
 @import '@/styles/recruit/position/index.scss'
-</style>
+</style>

+ 5 - 3
src/views/recruit/personal/position/components/poster.vue

@@ -7,8 +7,6 @@
         <!-- 职位名称+薪资 -->
         <div class="d-flex justify-space-between mx-5">
           <h2 class="JobName ellipsis">{{ formatName(info.name) }}</h2>
-          <span v-if="!info.payFrom && !info.payTo" class="salary">面议</span>
-          <span v-else class="salary">{{ info.payFrom ? info.payFrom + '-' : '' }}{{ info.payTo }}{{ positionInfo.payName ? '/' + positionInfo.payName : '' }}</span>
         </div>
         <!-- 地区、工作经验、学历 -->
         <div class="d-flex justify-space-between mx-5">
@@ -21,13 +19,17 @@
               <span v-if="i !== desc.length - 1 && (positionInfo[k.value] || k.value === 'areaName')" class="septal-line"></span>
             </div>
           </div>
+          <div>
+            <span v-if="!info.payFrom && !info.payTo" class="salary">面议</span>
+            <span v-else class="salary">{{ info.payFrom ? info.payFrom + '-' : '' }}{{ info.payTo }}{{ positionInfo.payName ? '/' + positionInfo.payName : '' }}</span>
+          </div>
         </div>
         <!-- 企业信息 -->
         <div class="radius pa-3 mx-5 mt-5 d-flex align-center" style="background-color: #5baea6;">
           <v-avatar style="border: 2px solid #fff;" size="68">
             <img crossOrigin="anonymous" :src="info.enterprise.logoUrl" alt="" style="width: 68px; height: 68px;">
           </v-avatar>
-          <div class="enterprise-name ml-5 ellipsis" style="width: 65%;">{{ formatName(info.enterprise?.anotherName || info.enterprise?.name) }}</div>
+          <div class="enterprise-name ml-5" style="width: 65%;">{{ formatName(info.enterprise?.anotherName || info.enterprise?.name) }}</div>
           <div style="flex: 1;" class="text-right enterprise-name">{{ info.areaName }}</div>
         </div>
         <div class="mx-5 mt-3">

+ 17 - 18
src/views/recruit/personal/position/index.vue

@@ -1,21 +1,21 @@
 <!-- 检索列表页 - 职位检索 -->
 <template>
   <div class="default-width">
-    <buttons :current="1" style="position: sticky;"></buttons>
+    <buttons :current="1" style="position: sticky;" class="mx-4"></buttons>
     <div class="position-content">
-      <div class="pb-3 pt-5" style="z-index: 998; background-color: #fff">
+      <div class="pb-3 pt-5 mx-4" style="z-index: 998; background-color: #fff">
         <div class="stickyBox mb-5">
           <headSearch
             v-model="headSearchText"
             @handleSearch="val => handleQueryChange('content', val)"
-          ></headSearch>
-        </div>
-        <!-- <cityFilter class="mx-5 mb-3" ref="cityFilterRef" @change="handleQueryChange"></cityFilter> -->
-        <div class="d-flex justify-space-between mx-5">
-          <conditionFilter ref="conditionFilterRef" showCitySelect :showFilterList="showFilterList" @reset="handleReset" @change="handleQueryChange"></conditionFilter>
-        </div>
+        ></headSearch>
       </div>
-      <div class="mt-3">
+      <!-- <cityFilter class="mx-5 mb-3" ref="cityFilterRef" @change="handleQueryChange"></cityFilter> -->
+      <div class="d-flex justify-space-between mx-5">
+        <conditionFilter ref="conditionFilterRef" showCitySelect :showFilterList="showFilterList" @reset="handleReset" @change="handleQueryChange"></conditionFilter>
+      </div>
+    </div>
+      <div class="mt-3 mx-4">
         <Empty v-if="!items?.length" :message="noParams? '请输入或选择对应的条件查找职位' : '没有相关的职位,请更换搜索条件后再试'"></Empty>
         <PositionLongStrip v-else :items="items"></PositionLongStrip>
         <CtPagination
@@ -45,7 +45,6 @@ import { dealDictObjData } from '@/utils/position'
 import { useRoute, useRouter } from 'vue-router'
 defineOptions({name: 'retrieval-position-page'})
 const route = useRoute(); const router = useRouter()
-// const cityFilterRef = ref()
 const conditionFilterRef = ref()
 const showFilterList = [
   { key: 'positionId', isSingle: true },
@@ -54,20 +53,16 @@ const showFilterList = [
   { key: 'eduType' },
   { key: 'jobType' },
   { key: 'scale' },
-  { key: 'industryIds' },
-  // { key: 'financingStatus' },
+  { key: 'industryIds' }
 ]
 
 const pageInfo = { pageNo: 1, pageSize: 20}
 const items = ref([])
 const total = ref(0)
 let routeQuery = (route?.query && route.query && Object.keys(route?.query).length) ? reactive(route.query) : reactive({})
-// routeQuery.date = new Date().getTime()
-// router.push({ path: route.path, routeQuery })
-// if (routeQuery?.length) router.replace({ path: route.path, routeQuery })
 
 const noParams = ref(true)
-const headSearchText = ref(routeQuery?.content || '')
+const headSearchText = ref(routeQuery?.content ? routeQuery?.content.includes('&') ? decodeURIComponent(routeQuery.content) : routeQuery.content : '')
 
 // 职位搜索
 const getData = async () => {
@@ -79,7 +74,10 @@ const getData = async () => {
     const passingOneId = ['positionId'] // 单选且传递整型
     Object.keys(routeQuery).forEach(key => {
       if (routeQuery[key] === '' || key === 'date') return
-      else if (passingStrings.includes(key)) routerParams[key] = routeQuery[key] // 传给后端字符串
+      else if (passingStrings.includes(key)) { // 传给后端字符串
+        if (key === 'content') routerParams[key] = decodeURIComponent(routeQuery[key])
+        else routerParams[key] = routeQuery[key]
+      }
       else if (passingOneId.includes(key)) routerParams[key] = +routeQuery[key] // 传给后端单选且传递整型
       else routerParams[key] = routeQuery[key].split('_').filter(e => e !== '9999') // 传给后端Arr
     })
@@ -117,6 +115,7 @@ const updateRouter = () => {
     }, {})
   }
   query.date = new Date().getTime() // 用于前端刷新路由参数
+  if (query?.content && query.content.includes('%')) query.content = decodeURIComponent(query.content)
   router.push({ path: route.path, query })
   pageInfo.pageNo = 1
   // getData()
@@ -155,4 +154,4 @@ const handleChangePage = (index) => {
   width: 0;
   height: 0;
 }
-</style>
+</style>

+ 3 - 7
src/views/recruit/personal/recommend/components/positionList.vue

@@ -3,7 +3,7 @@
     <div class="position-box">
       <div class="sub-li"
         v-for="(item, index) in list" :key="index"
-        :class="{'chosen': chosenIndex === index}"
+        :class="[{'chosen': chosenIndex === index}, item.active ? 'elevation-8' : 'elevation-3']"
         :style="`margin-top: ${index ? '12px' : '0'}`"
         @mouseenter="item.active = true" @mouseleave="item.active = false"
         @click="handleClick(item, index)"
@@ -60,7 +60,7 @@
 defineOptions({ name: 'position-card-item' })
 import { ref, watch } from 'vue'
 import { formatName } from '@/utils/getText'
-import { spaces } from '@/utils/index.js'
+// import { spaces } from '@/utils/index.js'
 
 const emit = defineEmits([''])
 const props = defineProps({
@@ -111,10 +111,6 @@ const handleClick = (item, index) => {
   // transition: all .2s linear;
   background-color: #fff;
   border: 1px solid #fff;
-  &:hover {
-    box-shadow: 0 16px 40px 0 rgba(153, 153, 153, .3);
-    .salary { color: var(--v-error-base) !important; }
-  }
 }
 .job-info {
   position: relative;
@@ -193,4 +189,4 @@ const handleClick = (item, index) => {
     color: #404040;
   }
 }
-</style>
+</style>

+ 15 - 0
src/views/recruit/personal/recommend/index.vue

@@ -18,12 +18,16 @@
               :defaultWidth="false"
               :showContentRight="false"
               :propJobId="jobId"
+              :isRecommend="true"
+              @preview="handlePreview"
             ></positionItemDetail>
           </div>
         </div>
       </div>
     </template>
   </div>
+
+  <PreviewImage v-if="showPreview" :urlList="[previewSrc]" :fileName="fileName" @close="showPreview = !showPreview" />
 </template>
 
 <script setup>
@@ -77,6 +81,17 @@ const handleChangePage = () => {
   getList()
 }
 
+// 分享海报预览
+const showPreview = ref(false)
+const previewSrc = ref('')
+const fileName = ref('')
+const handlePreview = (url, filename) => {
+  if (!url) return Snackbar.warning('海报生成失败,请重新点击分享生成海报')
+  previewSrc.value = url
+  fileName.value = filename
+  showPreview.value = true
+}
+
 </script>
 
 <style scoped lang="scss">