Просмотр исходного кода

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

zhengnaiwen_citu 1 год назад
Родитель
Сommit
3eaae0111f

+ 8 - 0
src/api/enterprise.js

@@ -14,4 +14,12 @@ export const getEnterpriseDetails = async (params) => {
     url: '/app-api/menduner/system/enterprise/detail',
     params
   })
+}
+
+// 效验求职者是否关注该企业
+export const getEnterpriseSubscribeCheck = async (params) => {
+  return await request.get({
+    url: '/app-api/menduner/system/person/enterprise/subscribe/check',
+    params
+  })
 }

+ 25 - 0
src/api/position.js

@@ -47,3 +47,28 @@ export const getJobAdvertisedSearch = async (params) => {
     params
   })
 }
+
+// 效验求职者是否收藏该职位
+export const getJobFavoriteCheck = async (params) => {
+  return await request.get({
+    url: '/app-api/menduner/system/person/job/favorite/check',
+    params
+  })
+}
+
+// 根据企业id统计职位类型的数量
+export const getJobAdvertisedPositionCount = async (params) => {
+  return await request.get({
+    url: '/app-api/menduner/system/job/advertised/get/position/count',
+    params
+  })
+}
+
+
+// 根据企业id获取企业职位区域
+export const getJobAreaByEnterpriseId = async (params) => {
+  return await request.get({
+    url: '/app-api/menduner/system/job/advertised/get/area/count',
+    params
+  })
+}

+ 107 - 0
src/components/CtVuetify/CtPagination/index.vue

@@ -0,0 +1,107 @@
+<template>
+  <div class="d-flex justify-center align-center pa-5">
+    <v-pagination
+      v-model="currentPage"
+      :length="pageLength"
+      @update:model-value="handleCurrentChange"
+      v-bind="$attrs"
+      :total-visible="props.totalVisible"
+      active-color="primary"
+      color="primary"
+      size="small"
+    ></v-pagination>
+    <span style="color: #666;">共{{ props.total }}条数据</span>
+    <div class="search px-3">
+      <v-text-field
+        v-model="currentJump"
+        type="number"
+        hide-details
+        variant="outlined"
+        density="compact"
+        label=""
+        placeholder=""
+        hide-spin-buttons
+      ></v-text-field>
+    </div>
+    <div>
+      <v-btn color="primary" rounded class="half-button" @click="handleCurrentJump">跳转</v-btn>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'pagination-page' })
+import { ref, watch, computed } from 'vue'
+const emits = defineEmits(['handleChange'])
+
+const props = defineProps({
+  total: {
+    type: [String, Number],
+    default: 0
+  },
+  page: {
+    type: Number,
+    default: 1
+  },
+  limit: {
+    type: Number,
+    default: 10
+  },
+  pageSizes: {
+    type: Array,
+    default: () => [5, 10, 15, 20]
+  },
+  layout: {
+    type: String,
+    default: 'total, prev, pager, next, jumper'
+  },
+  autoScroll: {
+    type: Boolean,
+    default: true
+  },
+  hidden: {
+    type: Boolean,
+    default: false
+  },
+  totalVisible: {
+    type: Number,
+    default: 7
+  }
+})
+
+const currentPage = ref(1)
+const currentJump = ref(null)
+currentPage.value = props.page
+
+watch(() => props.page, (newVal) => {
+  currentPage.value = newVal
+}, { deep: true })
+
+
+const pageLength = computed(() => {
+  return Math.ceil(props.total / props.limit)
+})
+
+const handleCurrentChange = () => {
+  emits('handleChange', currentPage.value)
+}
+
+const handleCurrentJump = () => {
+  if (currentJump.value > pageLength.value) currentJump.value = pageLength.value
+  if (currentJump.value < 1) currentJump.value = 1
+  currentPage.value = +currentJump.value
+  emits('handleChange', currentPage.value)
+}
+</script>
+
+<style lang="scss" scoped>
+.search {
+  width: 80px;
+  height: 50px;
+  display: flex;
+  align-items: center;
+}
+// ::v-deep  .v-text-field--outlined.v-input--dense.v-text-field--outlined > .v-input__control > .v-input__slot {
+//   min-height: 35px;
+// }
+</style>

+ 175 - 12
src/components/Enterprise/components/positions.vue

@@ -1,20 +1,138 @@
 <template>
-  <div>
-    <div class="top">检索</div>
-    <div class="bottom">
-      <div v-for="(val, i) in list" :key="i">
-        <div class="d-flex justify-space-between">
+  <div class="top">
+    <div class="d-flex">
+      <div class="font-weight-bold position-category-left">职位类别:</div>
+      <div class="position-category-right">
+        <span 
+          :class="['category-item', {'default-active': k.active}]" 
+          v-for="k in positionCategory" 
+          :key="k.id"
+          @mouseenter="k.active = true"
+          @mouseleave="k.active = false"
+        >{{ k.id === '-1' ? `${k.label}` : `${k.label} (${k.number})` }}</span>
+      </div>
+    </div>
+    <div class="d-flex mt-3">
+      <areaType :list="areaList"></areaType>
+      <expType></expType>
+      <educationType></educationType>
+      <payScope></payScope>
+      <div style="width: 200px; height: 20px;">
+        <v-text-field  variant="outlined" placeholder="请输入职位名称">
+          <template #append-inner>
+            <v-btn color="primary" size="x-small">搜索</v-btn>
+          </template>
+        </v-text-field>
+      </div>
+    </div>
+  </div>
+  <div class="bottom mt-5">
+    <div 
+      v-for="(val, i) in list" 
+      :key="i" 
+      :class="['bottom-item', {'border-bottom-dashed': i !== list.length -1}, 'd-flex', 'justify-space-between', 'cursor-pointer']" 
+      @mouseenter="val.active = true"
+      @mouseleave="val.active = false"
+      @click="handlePosition(val)"
+    >
+      <div>
           <p :class="['name', {'default-active': val.active }]">{{ val.name }}</p>
+          <div style="line-height: 40px;">
+            <span v-for="k in desc" :key="k.mdi" class="mr-5">
+              <v-icon color="#666" size="15">{{ k.mdi }}</v-icon>
+              <span class="ml-1 tag-text">{{ val[k.value] }}</span>
+            </span>
+          </div>
+      </div>
+      <div v-if="!val.active" class="text-right">
           <p class="salary">{{ val.payFrom }}-{{ val.payTo }}k/{{ val.payName }}</p>
-        </div>
+          <div class="update-time">{{ timesTampChange(val.updateTime) }} 刷新</div>
+      </div>
+      <div v-else class="account-info">
+          <v-avatar image="https://cdn.vuetifyjs.com/images/john.jpg"></v-avatar>
+          <span class="account-label">陈北方 · 人事经理</span>
+          <span>
+            <v-btn class="half-button" color="primary" size="small">立即沟通</v-btn>
+          </span>
       </div>
     </div>
+    <MPagination
+        :total="total"
+        :page="pageInfo.current"
+        :limit="pageInfo.size"
+        @handleChange="handleChangePage"
+    ></MPagination>
   </div>
 </template>
 
 <script setup>
 defineOptions({ name: 'recruitment-positions'})
-const list = [
+import { ref } from 'vue'
+import { timesTampChange } from '@/utils/date'
+import { getDict } from '@/hooks/web/useDictionaries'
+import { getJobAdvertisedPositionCount, getJobAreaByEnterpriseId } from '@/api/position'
+import MPagination from '@/components/CtVuetify/CtPagination'
+import expType from '@/views/recruit/position/components/conditionFilter/expType.vue'
+import educationType from '@/views/recruit/position/components/conditionFilter/educationType.vue'
+import payScope from '@/views/recruit/position/components/conditionFilter/payScope.vue'
+import areaType from '@/views/recruit/position/components/conditionFilter/areaType.vue'
+
+const props = defineProps({
+  info: {
+    type: Object,
+    default: () => {}
+  }
+})
+
+const total = ref(12)
+const pageInfo = ref({
+  size: 3,
+  current: 1
+})
+
+const handleChangePage = (index) => {
+  console.log(index, 'handle-page')
+}
+
+const handlePosition = (val) => {
+  window.open(`/recruit/position/details/${val.positionId}`)
+}
+
+// 行业列表
+const industryList = ref([])
+const getDictData = async () => {
+  const { data } = await getDict('menduner_industry_type', {}, 'industryList')
+  industryList.value = data
+}
+getDictData()
+
+// 职位类别&工作地点
+const positionCategory = ref([])
+const areaList = ref([])
+const getData = async () => {
+  const data = await getJobAdvertisedPositionCount({ enterpriseId: props.info.enterprise.id })
+  areaList.value = await getJobAreaByEnterpriseId({ enterpriseId: props.info.enterprise.id })
+  const list = data.map(val => {
+    const value = industryList.value.find(e => Number(e.id) === Number(val.key))
+    return { id: value.id, label: value.nameCn, number: val.value, active: false }
+  })
+  positionCategory.value = [{ id: '-1', label: '全部', active: true }, ...list]
+}
+getData()
+
+const list = ref([
+  {
+    name: '产品经理',
+    payFrom: 6,
+    payTo: 11,
+    payName: '月',
+    updateTime: 1716175909224,
+    areaName: '广州',
+    eduName: '本科',
+    expName: '1-3年',
+    active: false,
+    positionId: 4
+  },
   {
     name: '产品经理',
     payFrom: 6,
@@ -24,7 +142,8 @@ const list = [
     areaName: '广州',
     eduName: '本科',
     expName: '1-3年',
-    active: false
+    active: false,
+    positionId: 4
   },
   {
     name: '产品经理',
@@ -35,12 +154,23 @@ const list = [
     areaName: '广州',
     eduName: '本科',
     expName: '1-3年',
-    active: false
+    active: false,
+    positionId: 4
   }
+])
+const desc = [
+  { mdi: 'mdi-map-marker-outline', value: 'areaName' },
+  { mdi: 'mdi-school-outline', value: 'eduName' },
+  { mdi: 'mdi-clock-time-ten-outline', value: 'expName' }
 ]
 </script>
 
 <style scoped lang="scss">
+.bottom-item {
+  width: 100%;
+  height: 68px;
+  margin-bottom: 12px;
+}
 .name {
   position: relative;
   max-width: 200px;
@@ -49,9 +179,6 @@ const list = [
   text-overflow: ellipsis;
   white-space: nowrap;
   font-weight: 600;
-  &:hover {
-    color: var(--v-primary-base);
-  }
 }
 .salary {
   font-size: 16px;
@@ -60,4 +187,40 @@ const list = [
   line-height: 22px;
   flex: none;
 }
+.tag-text {
+  color: #222;
+  font-size: 14px;
+}
+.update-time {
+  color: #666;
+  font-size: 14px;
+  line-height: 40px;
+}
+.account-info {
+  line-height: 52px;
+  .account-label {
+    color: #666;
+    font-size: 14px;
+    font-weight: 600;
+    margin: 0 10px;
+  }
+}
+.position-category-left {
+  width: 80px;
+}
+.position-category-right {
+  flex: 1;
+}
+.category-item {
+  margin-right: 20px;
+  font-size: 15px;
+  color: #888;
+  font-weight: 600;
+  cursor: pointer;
+}
+:deep(.v-field__input) {
+  height: 28px;
+  padding: 0 0 0 10px;
+  font-size: 12px
+}
 </style>

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

@@ -16,8 +16,13 @@
           <div class="tools-box-number">{{ info.jobAdvertisedCount }}</div>
           <div class="tools-box-text">职位在招</div>
         </div>
-        <v-icon class="ml-5 mr-2" size="25" :color="info.attention ? 'primary' : ''">{{ info.attention ? 'mdi-heart' : 'mdi-heart-outline' }}</v-icon>
-        <v-icon size="25">mdi-alert-outline</v-icon> 
+        <!-- 是否关注该企业 -->
+        <v-tooltip location="bottom">
+          <template v-slot:activator="{ props }">
+            <v-icon v-bind="props" class="ml-5 mr-2" size="25" :color="isCollection ? 'primary' : ''">{{ isCollection ? 'mdi-heart' : 'mdi-heart-outline' }}</v-icon>
+          </template>
+          <span>关注该企业</span>
+        </v-tooltip>
       </div>
     </div>
     <v-divider></v-divider>
@@ -29,7 +34,7 @@
       <div class="d-flex" v-if="Object.keys(info).length">
         <div class="content-left">
           <EnterpriseIntroduction v-if="tab === 1" :info="info" />
-          <recruitmentPositions v-else />
+          <recruitmentPositions v-else :info="info"/>
         </div>
         <div class="content-right">
           <div class="welfare">
@@ -78,7 +83,7 @@ defineOptions({ name: 'enterprise-details'})
 import { ref } from 'vue'
 import EnterpriseIntroduction from './components/introduction.vue'
 import recruitmentPositions from './components/positions.vue'
-import { getEnterpriseDetails } from '@/api/enterprise'
+import { getEnterpriseDetails, getEnterpriseSubscribeCheck } from '@/api/enterprise'
 import { timesTampChange } from '@/utils/date'
 import { dealDictData } from '@/views/recruit/position/components/dict.js'
 
@@ -101,9 +106,17 @@ const getDetails = async () => {
   data.business.establishmentTime = time.slice(0, 10)
 
   info.value = { ...data, ...dealDictData({}, data.enterprise) }
+  getCollectionStatus(id)
 }
 getDetails()
 
+// 效验求职者是否关注该企业
+const isCollection = ref(false)
+const getCollectionStatus = async (id) => {
+  const data = await getEnterpriseSubscribeCheck({ enterpriseId: id })
+  isCollection.value = data
+}
+
 // 工商信息
 const businessList = [
   { label: '企业类型:', value: 'type' },
@@ -119,6 +132,10 @@ const recruitmentSpecialist = [
   { name: '徐有道', position: '招聘专员', desc: '正在招聘“运营专员”等职位', avatar: 'https://cdn.vuetifyjs.com/images/john.jpg' },
   { name: '方晓', position: '人事经理', desc: '正在招聘“运营专员”等职位', avatar: 'https://avatars0.githubusercontent.com/u/9064066?v=4&s=460' }
 ]
+
+// 有tab的是首页中热门企业模块
+const savedTab = new URLSearchParams(window.location.search).get('tab')
+if (savedTab) tab.value = 2
 </script>
 
 <style scoped lang="scss">

+ 6 - 1
src/components/Enterprise/hotPromoted.vue

@@ -27,7 +27,7 @@
         </li>
       </ul>
       <div class="moreBtn">
-        <v-btn class="buttons" color="primary" variant="outlined">{{ $t('position.moreBtn') }}</v-btn>
+        <v-btn class="buttons" color="primary" variant="outlined" @click="handleMoreEnterprise(item)">{{ $t('position.moreBtn') }}</v-btn>
       </div>
     </div>
   </div>
@@ -64,6 +64,11 @@ const handleClickPosition = (k) => {
 const handleClickEnterprise = (item) => {
   window.open(`/enterprise/details/${item.id}`)
 }
+
+// 查看更多职位
+const handleMoreEnterprise = (item) => {
+  window.open(`/enterprise/details/${item.id}?tab=recruitmentPositions`)
+}
 </script>
 
 <style lang="scss" scoped>

+ 5 - 1
src/components/Enterprise/info.vue

@@ -4,7 +4,7 @@
     <div style="height: 50px;">
       <v-img class="float-left" :src="props.info.enterprise.logoUrl" :width="45" height="45"></v-img>
       <div class="ml-3 float-left">
-        <p class="enterprise-name">{{ props.info.enterprise.anotherName }}</p>
+        <p class="enterprise-name cursor-pointer" @click="handleEnterprise">{{ props.info.enterprise.anotherName }}</p>
         <v-icon color="primary" size="20">mdi-shield-check</v-icon> <!-- mdi-shield-remove -->
         <span style="color: var(--v-primary-base);font-size: 14px;">已认证</span>
       </div>
@@ -44,6 +44,10 @@ const getData = async () => {
   obj.value = dealDictData(obj.value, prise)
 }
 getData()
+
+const handleEnterprise = () => {
+  window.open(`/enterprise/details/${props.info.enterprise.id}`)
+}
 </script>
 
 <style lang="scss" scoped>

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

@@ -1,7 +1,7 @@
 <template>
   <div class="position-box">
     <h4 class="mb-3">相似职位</h4>
-    <div v-for="(item, index) in props.list" :key="index" class="mb-2 cursor-pointer">
+    <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>
       <div :class="['enterprise', {'border-bottom-dashed': index !== list.length - 1}]">
@@ -23,6 +23,9 @@ const props = defineProps({
     default: () => []
   }
 })
+const handlePosition = (item) => {
+  window.open(`/recruit/position/details/${item.positionId}`)
+}
 </script>
 
 <style lang="scss" scoped>

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

@@ -83,8 +83,7 @@ const handlePosition = (item) => {
 .textColor666 { color: #666; }
 .positionItem {
   width: 884px;
-  margin-bottom: 20px;
-  border-radius: 12px;
+  margin-bottom: 12px;
   border-radius: 12px;
   padding: 0;
   overflow: hidden;

+ 4 - 3
src/components/jobTypeCard/index.vue

@@ -32,7 +32,7 @@
         </div>
       </div>
     </v-card>
-    <v-card v-if="rightObj.show" class="card rightCardBox ml-3">
+    <v-card v-if="rightObj.show" class="card rightCardBox">
       <div class="rightCard">
         <div class="categoryName">{{ rightObj.data.nameCn }}</div>
         <div v-for="(item, index) in rightObj.data.children" :key="item.id">
@@ -47,7 +47,7 @@
         </div>
       </div>
     </v-card>
-    <v-card height="392px" v-if="isPage && !rightObj.show" class="card rightCardBox ml-3">
+    <v-card height="392px" v-if="isPage && !rightObj.show" class="card rightCardBox">
       <v-carousel show-arrows="hover" cycle>
         <v-carousel-item v-for="(item, i) in carouselList" :key="i">
           <div style="height: 392px; overflow: hidden;">
@@ -159,6 +159,7 @@ const carouselList = ref([
     }
     .rowItem { justify-content: space-between; }
   }
+  .rightCardBox { margin-left: 4px; }
   .rightCard {
     height: 242px;
     width: 525px;
@@ -235,7 +236,7 @@ const carouselList = ref([
       }
     }
   }
-  .rightCardBox { flex: 1; }
+  .rightCardBox { flex: 1; margin-left: 12px;}
   .rightCard {
     height: 384px;
     // min-width: 786px;

+ 0 - 2
src/config/axios/service.js

@@ -54,12 +54,10 @@ service.interceptors.request.use(
       ;(config).headers.Authorization = 'Bearer ' + getToken() // 让每个请求携带自定义token
     }
     // 设置租户
-    console.log('tenantEnable', import.meta.env.VITE_TENANTCODE)
     if (tenantEnable && tenantEnable === 'true') {
       const tenantId = import.meta.env.VITE_TENANTCODE
       if (tenantId) config.headers['tenant-id'] = tenantId
     }
-    console.log(config)
     const params = config.params || {}
     const data = config.data || false
     if (

+ 1 - 1
src/views/login/index.vue

@@ -59,7 +59,7 @@ import Snackbar from '@/plugins/snackbar'
 defineOptions({ name: 'login-index' })
 
 const router = useRouter()
-console.log('更新')
+
 const phone = ref()
 let isPhone = ref(false)
 const handlePhone = () => {

+ 20 - 4
src/views/recruit/position/components/conditionFilter/JobType.vue

@@ -1,19 +1,35 @@
 <template>
-  <commonStyle btnTitle="求职类型">
-    <div></div>
+  <commonStyle btnTitle="求职类型" :close-on-content-click="false">
+    <v-list>
+      <v-list-item
+        color="primary"
+        :active="selectedItems.includes(item.value)"
+        v-for="item in items" :key="item.id" :value="item.value"
+        @click="handle(item.value)"
+      >
+        <v-list-item-title>{{ item.label }}</v-list-item-title>
+      </v-list-item>
+    </v-list>
   </commonStyle>
 </template>
 <script setup>
 import commonStyle from './commonStyle.vue'
 import { getDict } from '@/hooks/web/useDictionaries'
-import { ref } from 'vue';
+import { ref, defineEmits } from 'vue';
 
 defineOptions({name: 'conditionFilter-JobType'})
+const emits = defineEmits(['selectedItems'])
 let items = ref()
-getDict('menduner_job_type').then(({ data }) => { // 求职类型
+const selectedItems = ref([])
+getDict('menduner_job_type').then(({ data }) => {
   data = data?.length && data || []
   items.value = data
 })
+const handle = (val) => {
+  if (selectedItems.value.includes(val)) selectedItems.value = selectedItems.value.filter(i => i !== val)
+  else selectedItems.value.push(val)
+  emits('selectedItems', selectedItems.value)
+}
 </script>
 <style lang="scss" scoped>
 </style>

+ 43 - 0
src/views/recruit/position/components/conditionFilter/areaType.vue

@@ -0,0 +1,43 @@
+<template>
+  <commonStyle btnTitle="工作地点">
+    <v-list>
+      <v-list-item
+        color="primary"
+        :active="selectedItems.includes(item.value)"
+        v-for="item in items" :key="item.id" :value="item.value"
+        @click="handle(item.value)"
+      >
+        <v-list-item-title>{{ item.label }}</v-list-item-title>
+      </v-list-item>
+    </v-list>
+  </commonStyle>
+</template>
+
+<script setup>
+import commonStyle from './commonStyle.vue'
+import { getDict } from '@/hooks/web/useDictionaries'
+import { ref, defineEmits } from 'vue';
+
+defineOptions({name: 'conditionFilter-area-type'})
+const emits = defineEmits(['selectedItems'])
+const props = defineProps({
+  list: Array
+})
+
+let items = ref()
+let selectedItems = ref([])
+getDict('menduner_area_type', {}, 'areaList').then(({ data }) => {
+  data = data?.length && data || []
+  items.value = props.list.map(e => {
+    const { id, parentId, type, name: label } = data.find(k => Number(k.id) === Number(e.key))
+    return { id, label, number: e.value, parentId, type }
+  })
+})
+const handle = (val) => {
+  // 单选
+  selectedItems.value = [val]
+  emits('selectedItems', selectedItems.value)
+}
+</script>
+<style lang="scss" scoped>
+</style>

+ 29 - 22
src/views/recruit/position/components/conditionFilter/commonStyle.vue

@@ -1,20 +1,28 @@
 <template>
-  <v-menu open-on-hover>
-    <template v-slot:activator="{ props }">
-      <div class="d-flex align-center" v-bind="props">
-        <div class="btn" @mouseover="drawer = true" @mouseleave="drawer = false">
+  <v-menu open-on-hover v-bind="$attrs">
+    <template v-slot:activator="{ isActive, props }">
+      <v-btn
+        class="mr-3"
+        density="comfortable"
+        :append-icon="isActive ? 'mdi mdi-menu-up' : 'mdi mdi-menu-down'"
+        color="primary" variant="tonal"
+        v-bind="props"
+      >
+        {{ defineProps.btnTitle }}
+      </v-btn>
+      <!-- <div class="d-flex align-center">
+        <div class="btn" v-bind="props">
           <span class="mr-2">{{ defineProps.btnTitle }}</span>
-          <span v-if="drawer" class="mdi mdi-menu-up"></span>
-          <span v-if="!drawer" class="mdi mdi-menu-down"></span>
+          <span v-if="isActive" class="mdi mdi-menu-up"></span>
+          <span v-if="!isActive" class="mdi mdi-menu-down"></span>
         </div>
-      </div>
+      </div> -->
     </template>
     <!-- <jobTypeCard @click.stop=""></jobTypeCard> -->
     <slot></slot>
   </v-menu>
 </template>
 <script setup>
-import { ref } from 'vue';
 
 defineOptions({name: 'conditionFilter-index-page'})
 const defineProps = defineProps({
@@ -23,20 +31,19 @@ const defineProps = defineProps({
     default: 'Text'
   }
 })
-const drawer = ref(false)
 </script>
 <style lang="scss" scoped>
-.btn {
-  color: #333333;
-  background-color: var(--default-bgc);
-  padding: 4px 12px 4px 12px;
-  border-radius: 4px;
-  margin-right: 20px;
-  margin-bottom: 4px;
-  cursor: pointer;
-  &:hover {
-    color: var(--v-primary-base);
-    background-color: #d5e6e8;
-  }
-}
+// .btn {
+//   color: #333333;
+//   background-color: var(--default-bgc);
+//   padding: 4px 12px 4px 12px;
+//   border-radius: 4px;
+//   margin-right: 20px;
+//   margin-bottom: 4px;
+//   cursor: pointer;
+//   &:hover {
+//     color: var(--v-primary-base);
+//     background-color: #d5e6e8;
+//   }
+// }
 </style>

+ 20 - 4
src/views/recruit/position/components/conditionFilter/educationType.vue

@@ -1,19 +1,35 @@
 <template>
-  <commonStyle btnTitle="学历要求">
-    <div></div>
+  <commonStyle btnTitle="学历要求" :close-on-content-click="false">
+    <v-list>
+      <v-list-item
+        color="primary"
+        :active="selectedItems.includes(item.value)"
+        v-for="item in items" :key="item.id" :value="item.value"
+        @click="handle(item.value)"
+      >
+        <v-list-item-title>{{ item.label }}</v-list-item-title>
+      </v-list-item>
+    </v-list>
   </commonStyle>
 </template>
 <script setup>
 import commonStyle from './commonStyle.vue'
 import { getDict } from '@/hooks/web/useDictionaries'
-import { ref } from 'vue';
+import { ref, defineEmits } from 'vue';
 
 defineOptions({name: 'conditionFilter-educationType'})
+const emits = defineEmits(['selectedItems'])
 let items = ref()
-getDict('menduner_education_type').then(({ data }) => { // 学历要求
+const selectedItems = ref([])
+getDict('menduner_education_type').then(({ data }) => {
   data = data?.length && data || []
   items.value = data
 })
+const handle = (val) => {
+  if (selectedItems.value.includes(val)) selectedItems.value = selectedItems.value.filter(i => i !== val)
+  else selectedItems.value.push(val)
+  emits('selectedItems', selectedItems.value)
+}
 </script>
 <style lang="scss" scoped>
 </style>

+ 20 - 4
src/views/recruit/position/components/conditionFilter/expType.vue

@@ -1,19 +1,35 @@
 <template>
-  <commonStyle btnTitle="工作经验">
-    <div></div>
+  <commonStyle btnTitle="工作经验" :close-on-content-click="false">
+    <v-list>
+      <v-list-item
+        color="primary"
+        :active="selectedItems.includes(item.value)"
+        v-for="item in items" :key="item.id" :value="item.value"
+        @click="handle(item.value)"
+      >
+        <v-list-item-title>{{ item.label }}</v-list-item-title>
+      </v-list-item>
+    </v-list>
   </commonStyle>
 </template>
 <script setup>
 import commonStyle from './commonStyle.vue'
 import { getDict } from '@/hooks/web/useDictionaries'
-import { ref } from 'vue';
+import { ref, defineEmits } from 'vue';
 
 defineOptions({name: 'conditionFilter-expType'})
+const emits = defineEmits(['selectedItems'])
 let items = ref()
-getDict('menduner_exp_type').then(({ data }) => { // 工作经验
+const selectedItems = ref([])
+getDict('menduner_exp_type').then(({ data }) => {
   data = data?.length && data || []
   items.value = data
 })
+const handle = (val) => {
+  if (selectedItems.value.includes(val)) selectedItems.value = selectedItems.value.filter(i => i !== val)
+  else selectedItems.value.push(val)
+  emits('selectedItems', selectedItems.value)
+}
 </script>
 <style lang="scss" scoped>
 </style>

+ 20 - 4
src/views/recruit/position/components/conditionFilter/financingStatus.vue

@@ -1,19 +1,35 @@
 <template>
-  <commonStyle btnTitle="融资阶段">
-    <div></div>
+  <commonStyle btnTitle="融资阶段" :close-on-content-click="false">
+    <v-list>
+      <v-list-item
+        color="primary"
+        :active="selectedItems.includes(item.value)"
+        v-for="item in items" :key="item.id" :value="item.value"
+        @click="handle(item.value)"
+      >
+        <v-list-item-title>{{ item.label }}</v-list-item-title>
+      </v-list-item>
+    </v-list>
   </commonStyle>
 </template>
 <script setup>
 import commonStyle from './commonStyle.vue'
 import { getDict } from '@/hooks/web/useDictionaries'
-import { ref } from 'vue';
+import { ref, defineEmits } from 'vue';
 
 defineOptions({name: 'conditionFilter-financingStatus'})
+const emits = defineEmits(['selectedItems'])
 let items = ref()
-getDict('menduner_financing_status').then(({ data }) => { // 融资阶段
+const selectedItems = ref([])
+getDict('menduner_financing_status').then(({ data }) => {
   data = data?.length && data || []
   items.value = data
 })
+const handle = (val) => {
+  if (selectedItems.value.includes(val)) selectedItems.value = selectedItems.value.filter(i => i !== val)
+  else selectedItems.value.push(val)
+  emits('selectedItems', selectedItems.value)
+}
 </script>
 <style lang="scss" scoped>
 </style>

+ 19 - 3
src/views/recruit/position/components/conditionFilter/payScope.vue

@@ -1,19 +1,35 @@
 <template>
   <commonStyle btnTitle="薪资待遇">
-    <div></div>
+    <v-list>
+      <v-list-item
+        color="primary"
+        :active="selectedItems.includes(item.value)"
+        v-for="item in items" :key="item.id" :value="item.value"
+        @click="handle(item.value)"
+      >
+        <v-list-item-title>{{ item.label }}</v-list-item-title>
+      </v-list-item>
+    </v-list>
   </commonStyle>
 </template>
 <script setup>
 import commonStyle from './commonStyle.vue'
 import { getDict } from '@/hooks/web/useDictionaries'
-import { ref } from 'vue';
+import { ref, defineEmits } from 'vue';
 
 defineOptions({name: 'conditionFilter-payScope'})
+const emits = defineEmits(['selectedItems'])
 let items = ref()
-getDict('menduner_pay_scope').then(({ data }) => { // 薪资待遇
+let selectedItems = ref([])
+getDict('menduner_pay_scope').then(({ data }) => {
   data = data?.length && data || []
   items.value = data
 })
+const handle = (val) => {
+  // 单选
+  selectedItems.value = [val]
+  emits('selectedItems', selectedItems.value)
+}
 </script>
 <style lang="scss" scoped>
 </style>

+ 20 - 4
src/views/recruit/position/components/conditionFilter/scale.vue

@@ -1,19 +1,35 @@
 <template>
-  <commonStyle btnTitle="公司规模">
-    <div></div>
+  <commonStyle btnTitle="公司规模" :close-on-content-click="false">
+    <v-list>
+      <v-list-item
+        color="primary"
+        :active="selectedItems.includes(item.value)"
+        v-for="item in items" :key="item.id" :value="item.value"
+        @click="handle(item.value)"
+      >
+        <v-list-item-title>{{ item.label }}</v-list-item-title>
+      </v-list-item>
+    </v-list>
   </commonStyle>
 </template>
 <script setup>
 import commonStyle from './commonStyle.vue'
 import { getDict } from '@/hooks/web/useDictionaries'
-import { ref } from 'vue';
+import { ref, defineEmits } from 'vue';
 
 defineOptions({name: 'conditionFilter-scale'})
+const emits = defineEmits(['selectedItems'])
 let items = ref()
-getDict('menduner_scale').then(({ data }) => { // 公司规模
+const selectedItems = ref([])
+getDict('menduner_scale').then(({ data }) => {
   data = data?.length && data || []
   items.value = data
 })
+const handle = (val) => {
+  if (selectedItems.value.includes(val)) selectedItems.value = selectedItems.value.filter(i => i !== val)
+  else selectedItems.value.push(val)
+  emits('selectedItems', selectedItems.value)
+}
 </script>
 <style lang="scss" scoped>
 </style>

+ 16 - 4
src/views/recruit/position/components/details.vue

@@ -17,9 +17,9 @@
             <v-chip size="small" label v-for="(k, i) in info.tagList" :key="i" class="mr-1" color="primary">{{ k }}</v-chip>
           </div>
           <div class="banner-tools-btns float-right">
-            <v-btn class="half-button radius" color="warning" variant="outlined" prepend-icon="mdi-star-outline">收藏</v-btn>
-            <v-btn class="half-button mx-2 radius" color="success" variant="outlined">立即沟通</v-btn>
-            <v-btn class="half-button radius" color="primary" variant="outlined">投递简历</v-btn>
+            <v-btn class="button-item radius" color="warning" variant="outlined" :prepend-icon="isCollection ? 'mdi-heart' : 'mdi-heart-outline'">{{ isCollection ? '取消收藏': '收藏' }}</v-btn>
+            <v-btn class="button-item mx-2 radius" color="success" variant="outlined">立即沟通</v-btn>
+            <v-btn class="button-item radius" color="primary" variant="outlined">投递简历</v-btn>
           </div>
         </div>
         <v-divider></v-divider>
@@ -73,7 +73,7 @@ defineOptions({ name: 'position-details' })
 import { ref } from 'vue'
 import { useRouter } from 'vue-router'
 import { timesTampChange } from '@/utils/date'
-import { getPositionDetails, getSimilarPosition } from '@/api/position'
+import { getPositionDetails, getSimilarPosition, getJobFavoriteCheck } from '@/api/position'
 import { dealDictData } from '@/views/recruit/position/components/dict'
 import similarPositions from '@/components/Position/similarPositions.vue'
 import EnterpriseInfo from '@/components/Enterprise/info.vue'
@@ -107,6 +107,14 @@ const getSimilarPositionList = async () => {
 }
 
 getSimilarPositionList()
+
+// 效验求职者是否有收藏该职位
+const isCollection = ref(true)
+const getCollectionStatus = async () => {
+  const data = await getJobFavoriteCheck({ jobId: id })
+  isCollection.value = data
+}
+getCollectionStatus()
 </script>
 
 <style lang="scss" scoped>
@@ -128,6 +136,10 @@ getSimilarPositionList()
   max-width: 360px;
   vertical-align: middle;
 }
+.button-item {
+  width: 110px;
+  height: 36px
+}
 .salary {
   color: var(--v-error-base);
   line-height: 41px;

+ 113 - 0
src/views/recruit/position/components/rightRecommend.vue

@@ -0,0 +1,113 @@
+<template>
+  <v-card class="box">
+    <h4 class="h4">推荐职位</h4>
+    <div class="content">
+      <div v-for="item, index in list " :key="index" class="itemBox">
+        <div class="left">
+          <div>{{ item.aaa }}</div>
+          <div class="textColor666 size14">{{ item.bbb }}</div>
+        </div>
+        <div class="right">
+          <div style="color: #fe574a;">{{ item.ccc }}</div>
+          <div class="textColor666 size14">{{ item.ddd }}</div>
+        </div>
+      </div>
+    </div>
+  </v-card>
+</template>
+
+<script setup>
+defineOptions({name: 'retrieval-components-recommendedPositions'})
+const list = [
+  {
+    aaa: '职位名称',
+    bbb: '公司名称',
+    ccc: '9-12K',
+    ddd: '广州·越秀区',
+  },
+  {
+    aaa: '职位名称职位名称职位名称职位名称职位名称',
+    bbb: '公司名称公司名称公司名称公司名称公司名称',
+    ccc: '9-12K',
+    ddd: '广州·越秀区-广州·越秀区广州·越秀区',
+  },
+  // {
+  //   aaa: '职位名称',
+  //   bbb: '公司名称',
+  //   ccc: '9-12K',
+  //   ddd: '广州·越秀区',
+  // },
+  // {
+  //   aaa: '职位名称',
+  //   bbb: '公司名称',
+  //   ccc: '9-12K',
+  //   ddd: '广州·越秀区',
+  // },
+  // {
+  //   aaa: '职位名称',
+  //   bbb: '公司名称',
+  //   ccc: '9-12K',
+  //   ddd: '广州·越秀区',
+  // },
+  // {
+  //   aaa: '职位名称',
+  //   bbb: '公司名称',
+  //   ccc: '9-12K',
+  //   ddd: '广州·越秀区',
+  // },
+  // {
+  //   aaa: '职位名称',
+  //   bbb: '公司名称',
+  //   ccc: '9-12K',
+  //   ddd: '广州·越秀区',
+  // },
+  {
+    aaa: '职位名称',
+    bbb: '公司名称',
+    ccc: '9-12K',
+    ddd: '广州·越秀区',
+  }
+]
+</script>
+<style lang="scss" scoped>
+.textColor666 { color: #666; }
+.size14 { font-size: 14px; }
+.box {
+  width: 100%;
+  min-height: 100px;
+  border-radius: 8px;
+  .h4 {
+    padding: 12px 16px;
+    background: linear-gradient(90deg, #f5fcfc 0, #fcfbfa 100%);
+  }
+  .content {
+    padding: 0 8px;
+    .itemBox {
+      &:hover {
+        background-color: #f3f3f3;
+      }
+      display: flex;
+      justify-content: space-between;
+      padding: 8px 16px;
+      margin: 4px 0;
+      border-radius: 8px;
+      cursor: pointer;
+      .left {
+        width: 148px;
+        margin-right: 12px;
+      }
+      .right {
+        width: calc(100% - 160px);
+        text-align: right;
+      }
+      .left, .right {
+        div {
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+        }
+      }
+    }
+  }
+}
+</style>

+ 5 - 4
src/views/recruit/position/index.vue

@@ -3,17 +3,17 @@
   <div class="default-width">
     <div style="width: 100%; height: 20px;"></div>
     <v-card style="z-index: 998">
-      <div class="stickyBox my-5">
+      <div class="stickyBox my-3">
         <headSearch></headSearch>
       </div>
       <cityFilter class="mx-5 mb-3"></cityFilter>
-      <conditionFilter class="mx-5 mb-5"></conditionFilter>
+      <conditionFilter class="mx-5 mb-3"></conditionFilter>
     </v-card>
-    <div class="d-flex mt-5">
+    <div class="d-flex mt-3">
       <div class="mr-3">
         <PositionLongStrip :items="items"></PositionLongStrip>
       </div>
-      <div>右侧列表</div>
+      <rightRecommend></rightRecommend>
     </div>
     <CtPagination
       v-if="total > 0"
@@ -25,6 +25,7 @@
   </div>
 </template>
 <script setup>
+import rightRecommend from './components/rightRecommend'
 import cityFilter from './components/cityFilter'
 import conditionFilter from './components/conditionFilter'
 import headSearch from '@/components/headSearch'