Browse Source

个人中心-已投递、职位收藏、公司收藏列表展示

Xiao_123 11 tháng trước cách đây
mục cha
commit
32357079a1

+ 2 - 0
components.d.ts

@@ -32,6 +32,8 @@ declare module 'vue' {
     Introduction: typeof import('./src/components/Enterprise/components/introduction.vue')['default']
     Introduction: typeof import('./src/components/Enterprise/components/introduction.vue')['default']
     Item: typeof import('./src/components/Position/item.vue')['default']
     Item: typeof import('./src/components/Position/item.vue')['default']
     JobTypeCard: typeof import('./src/components/jobTypeCard/index.vue')['default']
     JobTypeCard: typeof import('./src/components/jobTypeCard/index.vue')['default']
+    LongCompany: typeof import('./src/components/Position/longCompany.vue')['default']
+    LongStrip: typeof import('./src/components/Position/longStrip.vue')['default']
     Positions: typeof import('./src/components/Enterprise/components/positions.vue')['default']
     Positions: typeof import('./src/components/Enterprise/components/positions.vue')['default']
     PreviewImg: typeof import('./src/components/PreviewImg/index.vue')['default']
     PreviewImg: typeof import('./src/components/PreviewImg/index.vue')['default']
     RadioGroup: typeof import('./src/components/FormUI/radioGroup/index.vue')['default']
     RadioGroup: typeof import('./src/components/FormUI/radioGroup/index.vue')['default']

+ 24 - 0
src/api/position.js

@@ -102,4 +102,28 @@ export const jobCvRelSend = async (data) => {
     url: '/app-api/menduner/system/job-cv-rel/send',
     url: '/app-api/menduner/system/job-cv-rel/send',
     data
     data
   })
   })
+}
+
+// 获取已投递的职位列表
+export const getJobDeliveryList = async (params) => {
+  return await request.get({
+    url: '/app-api/menduner/system/job-cv-rel/get/job/cv/page',
+    params
+  })
+}
+
+// 获取收藏的招聘职位列表
+export const getJobFavoriteList = async (data) => {
+  return await request.post({
+    url: '/app-api/menduner/system/person/get/job/favorite/page',
+    data
+  })
+}
+
+// 获取关注的企业列表
+export const getSubscribeEnterprise = async (data) => {
+  return await request.post({
+    url: '/app-api/menduner/system/person/get/enterprise/subscribe/page',
+    data
+  })
 }
 }

+ 108 - 0
src/components/Position/longCompany.vue

@@ -0,0 +1,108 @@
+<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="company-info-top" @click="handleClickEnterprise(item, 'briefIntroduction')">
+        <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 }">{{ item.anotherName }}</h3>
+          <p>{{ item.financingName }}<span class="mx-2">|</span>{{ item.industryName }}<span class="mx-2">|</span>{{ item.scaleName }}</p>
+        </div>
+        <div>
+          <v-chip color="primary" size="small" label>已收藏</v-chip>
+        </div>
+      </div>
+      <div class="company-info-bottom">
+        <div class="chipBox">
+          <v-chip class="mr-2" color="primary" size="x-small" label v-for="(k, i) in item.welfareList" :key="i">{{ k }}</v-chip>
+        </div>
+        <div class="position" @click="handleClickEnterprise(item, 'recruitmentPositions')">
+          查看全部职位
+          <v-icon>mdi-menu-right</v-icon>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'long-company-card'})
+defineProps({
+  list: Array
+})
+const handleClickEnterprise = (item, key) => {
+  window.open(`/company/details/${item.id}?key=${key}`)
+}
+</script>
+
+<style scoped lang="scss">
+.sub-li {
+  position: relative;
+  height: 130px;
+  border-radius: 12px;
+  padding: 0;
+  overflow: hidden;
+  transition: all .2s linear;
+  background-color: #fff;
+  cursor: pointer;
+  &:nth-child(4n) {
+    margin-right: 0;
+  }
+  &:hover {
+    box-shadow: 0 16px 40px 0 rgba(153, 153, 153, .3);
+  }
+}
+.company-info {
+  float: left;
+  margin-left: 16px;
+  width: 282px;
+}
+.company-info-top {
+  display: flex;
+  height: 76px;
+  padding: 16px 20px;
+  overflow: hidden;
+  justify-content: space-between;
+}
+.company-info h3 {
+  height: 22px;
+  font-size: 16px;
+  font-weight: 400;
+  color: #222;
+  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: #999;
+  line-height: 18px;
+}
+.company-info-bottom {
+  display: flex;
+  width: 100%;
+  padding: 16px 20px;
+  justify-content: space-between;
+  background-color: #f8fcfb;
+  .chipBox {
+    width: 70%;
+    overflow: hidden;
+    white-space: nowrap;
+  }
+  .position {
+    color: #666;
+    font-size: 14px;
+    cursor: pointer;
+    &:hover {
+      color: var(--v-primary-base);
+    }
+  }
+}
+</style>

+ 139 - 0
src/components/Position/longStrip.vue

@@ -0,0 +1,139 @@
+<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="info-header">
+        <div v-if="val.active" class="header-btn">
+          <v-btn class="half-button" color="primary" size="small">继续沟通</v-btn>
+          <v-btn v-if="props.showCancelBtn" class="half-button ml-3" color="primary" size="small" @click="handleCancel(val)">取消感兴趣</v-btn>
+        </div>
+        <div class="img-box">
+          <v-avatar :image="val.contact.avatars || 'https://minio.citupro.com/dev/menduner/7.png'" size="x-small"></v-avatar>
+          <span class="name">
+            <span class="mx-3">{{ val.contact.name }}</span>
+            <span class="gray">{{ val.contact.postNameCn }}</span>
+          </span>
+        </div>
+      </div>
+      <div class="info-content">
+        <div class="job-info">
+          <div class="job-name cursor-pointer">
+            <span class="mr-3 info-name">{{ val.job.name }}</span>
+            <span>[{{ 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>
+          </div>
+        </div>
+        <div class="company-info">
+          <v-img width="50" height="50" :src="val.enterprise.logoUrl || 'https://minio.citupro.com/dev/menduner/7.png'"></v-img>
+          <div class="ml-3">
+            <div class="cursor-pointer info-name">{{ val.enterprise.name }}</div>
+            <div class="mt-3">
+              <v-chip color="primary" label size="small" class="mr-3" v-for="k in desc" :key="k">{{ val.enterprise[k] }}</v-chip>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'longStrip'})
+import { getPersonJobUnfavorite } from '@/api/position'
+import { useI18n } from '@/hooks/web/useI18n'
+import Snackbar from '@/plugins/snackbar'
+
+const emits = defineEmits(['refresh'])
+const { t } = useI18n()
+const props = defineProps({
+  items: {
+    type: Array,
+    default: () => []
+  },
+  showCancelBtn: {
+    type: Boolean,
+    default: false
+  }
+})
+
+const desc = ['industryName', 'financingName', 'scaleName']
+
+const handleCancel = async (item) => {
+  if (!item.job.id) return Snackbar.warning(t('sys.api.operationFailed'))
+  await getPersonJobUnfavorite(item.job.id)
+  emits('refresh')
+  Snackbar.success(t('common.operationSuccessful'))
+}
+</script>
+
+<style scoped lang="scss">
+.position-item {
+  height: 144px;
+  background-color: #fff;
+  border-radius: 12px;
+  &:hover {
+    box-shadow: 0 16px 40px 0 rgba(153, 153, 153, .3);
+  }
+  .info-header {
+    height: 48px;
+    background: linear-gradient(90deg,#f5fcfc,#fcfbfa);
+    border-radius: 12px;
+    .img-box {
+      padding: 12px 24px;
+      .name {
+        color: #222;
+        font-weight: 400;
+        font-size: 13px;
+        .gray {
+          color: #666;
+        }
+      }
+    }
+    .header-btn {
+      padding: 10px 10px 0 0;
+      float: right;
+      .v-btn {
+        z-index: 1;
+      }
+    }
+  }
+  .info-content {
+    display: flex;
+    padding: 16px 24px;
+    justify-content: space-between;
+    .job-info {
+      width: 430px;
+      min-width: 430px;
+      max-width: 430px;
+      font-weight: 500;
+      font-size: 16px;
+      .job-name {
+        height: 22px;
+        line-height: 22px;
+        color: #222;
+        margin-bottom: 12px;
+      }
+      .job-other {
+        color: var(--v-error-base);
+        height: 22px;
+        line-height: 22px;
+      }
+    }
+    .company-info {
+      display: flex;
+      align-items: center
+    }
+    .interview-info {
+      color: #333;
+      font-size: 15px;
+    }
+  }
+}
+
+.info-name:hover {
+  color: var(--v-primary-base);
+}
+</style>

+ 1 - 1
src/config/axios/service.js

@@ -191,7 +191,7 @@ service.interceptors.response.use(
 
 
 const refreshToken = async () => {
 const refreshToken = async () => {
   axios.defaults.headers.common['tenant-id'] = import.meta.env.VITE_TENANTCODE
   axios.defaults.headers.common['tenant-id'] = import.meta.env.VITE_TENANTCODE
-  return await axios.post(base_url + '/system/auth/refresh-token?refreshToken=' + getRefreshToken())
+  // return await axios.post(base_url + '/app-api/menduner/system/auth/refresh-token?refreshToken=' + getRefreshToken())
 }
 }
 const handleAuthorized = () => {
 const handleAuthorized = () => {
   const { t } = useI18n()
   const { t } = useI18n()

+ 2 - 1
src/locales/en.js

@@ -25,7 +25,8 @@ export default {
     upload: 'Upload',
     upload: 'Upload',
     home: 'Home',
     home: 'Home',
     position: 'Position',
     position: 'Position',
-    company: 'Company'
+    company: 'Company',
+    operationSuccessful: 'Operation successful'
   },
   },
   sys: {
   sys: {
     api: {
     api: {

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

@@ -25,7 +25,8 @@ export default {
     upload: '上传',
     upload: '上传',
     home: '首页',
     home: '首页',
     position: '职位',
     position: '职位',
-    company: '公司'
+    company: '公司',
+    operationSuccessful: '操作成功'
   },
   },
   sys: {
   sys: {
     api: {
     api: {

+ 5 - 5
src/views/Home/personal/components/popularEnterprises.vue

@@ -24,7 +24,7 @@ const dictObj = reactive({
   area: [], // 地区
   area: [], // 地区
   financing: [] // 融资类型
   financing: [] // 融资类型
 })
 })
-const dictList = [
+const dictList = ref([
   { type: 'menduner_pay_unit', value: 'payUnit', key: 'payUnit', label: 'payName' },
   { type: 'menduner_pay_unit', value: 'payUnit', key: 'payUnit', label: 'payName' },
   { type: 'menduner_education_type', value: 'edu', key: 'eduType', label: 'eduName' },
   { type: 'menduner_education_type', value: 'edu', key: 'eduType', label: 'eduName' },
   { type: 'menduner_exp_type', value: 'exp', key: 'expType', label: 'expName' },
   { type: 'menduner_exp_type', value: 'exp', key: 'expType', label: 'expName' },
@@ -32,17 +32,17 @@ const dictList = [
   { type: 'menduner_financing_status', value: 'financing', key: 'financingStatus', label: 'financingName', isEnter: true },
   { type: 'menduner_financing_status', value: 'financing', key: 'financingStatus', label: 'financingName', isEnter: true },
   { type: 'menduner_scale', value: 'scale', key: 'scale', label: 'scaleName', isEnter: true },
   { type: 'menduner_scale', value: 'scale', key: 'scale', label: 'scaleName', isEnter: true },
   { type: 'menduner_industry_type', value: 'industry', key: 'industryId', label: 'industryName', params: {}, apiType: 'industryList', nameKey: 'nameCn', valueKey: 'id', isEnter: true }
   { type: 'menduner_industry_type', value: 'industry', key: 'industryId', label: 'industryName', params: {}, apiType: 'industryList', nameKey: 'nameCn', valueKey: 'id', isEnter: true }
-]
+])
 
 
 // 热门企业
 // 热门企业
 const getHotEnterpriseList = async () => {
 const getHotEnterpriseList = async () => {
   const { list } = await getHotEnterprise({ pageNo: 1, pageSize: 9 })
   const { list } = await getHotEnterprise({ pageNo: 1, pageSize: 9 })
-  dictList.forEach(item => {
+  dictList.value.forEach(item => {
     items.value = list.map(e => {
     items.value = list.map(e => {
       if (item.isEnter) {
       if (item.isEnter) {
         const valueKey = item.nameKey ? item.nameKey : 'label'
         const valueKey = item.nameKey ? item.nameKey : 'label'
         const idKey = item.valueKey ? item.valueKey : 'value'
         const idKey = item.valueKey ? item.valueKey : 'value'
-        e[item.label] = dictObj[item.value].find(k => Number(k[idKey]) === Number(e.enterprise[item.key]))[valueKey]
+        e[item.label] = dictObj[item.value].find(k => k[idKey] === e.enterprise[item.key])[valueKey]
       }
       }
       const list = e.jobList
       const list = e.jobList
       if (!item.isEnter) {
       if (!item.isEnter) {
@@ -56,7 +56,7 @@ const getHotEnterpriseList = async () => {
 
 
 // 字典
 // 字典
 const getDictList = async () => {
 const getDictList = async () => {
-  dictList.forEach(async (val) => {
+  dictList.value.forEach(async (val) => {
     const { data } = await getDict(val.type, val.params, val.apiType)
     const { data } = await getDict(val.type, val.params, val.apiType)
     dictObj[val.value] = data
     dictObj[val.value] = data
   })
   })

+ 10 - 37
src/views/PersonalCenter/components/posiitonItem.vue → src/views/PersonalCenter/components/communication.vue

@@ -49,8 +49,11 @@
   </div>
   </div>
 </template>
 </template>
 
 
-<script setup name="positionItem">
+<script setup>
+// 沟通过
+defineOptions({ name: 'position-communication' })
 import { ref } from 'vue'
 import { ref } from 'vue'
+// import { dealDictArrayData, dealDictObjData } from '@/views/recruit/position/components/dict'
 import Empty from '@/components/Empty'
 import Empty from '@/components/Empty'
 
 
 const props = defineProps({
 const props = defineProps({
@@ -60,6 +63,11 @@ const props = defineProps({
   }
   }
 })
 })
 
 
+// const items = ref([])
+// const page = ref({
+//   pageNo: 1,
+//   pageSize: 10
+// })
 const list = ref([
 const list = ref([
   {
   {
     contact: {
     contact: {
@@ -108,6 +116,7 @@ const desc = ['industryName', 'financingName', 'scaleName']
 const handleMouseEnter = (val) => {
 const handleMouseEnter = (val) => {
   if (props.tab !==3 ) val.active = true
   if (props.tab !==3 ) val.active = true
 }
 }
+
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
@@ -176,40 +185,4 @@ const handleMouseEnter = (val) => {
 .info-name:hover {
 .info-name:hover {
   color: var(--v-primary-base);
   color: var(--v-primary-base);
 }
 }
-
-
-// .position-item:not(.job-closed):hover {
-//   box-shadow: 0 16px 40px 0 hsla(0,0%,60%,.3);
-//   z-index: 3
-// }
-
-// .position-item.job-closed .img-box {
-//   position: relative
-// }
-
-// .position-item.job-closed .img-box:after {
-//   content: "";
-//   position: absolute;
-//   left: 0;
-//   top: 0;
-//   width: 100%;
-//   height: 48px;
-//   background: linear-gradient(90deg,#f5fcfc,#fcfbfa);
-//   opacity: .8
-// }
-
-// .position-item.job-closed .info-content {
-//   position: relative
-// }
-
-// .position-item.job-closed .info-content:after {
-//   content: "";
-//   position: absolute;
-//   left: 0;
-//   top: 0;
-//   width: 100%;
-//   height: 92px;
-//   background: #fff;
-//   opacity: .8
-// }
 </style>
 </style>

+ 47 - 0
src/views/PersonalCenter/components/companyCollection.vue

@@ -0,0 +1,47 @@
+<template>
+  <div>
+    <div v-if="items.length">
+      <LongCompany :list="items"></LongCompany>
+      <CtPagination
+        :total="total"
+        :page="page.pageNo"
+        :limit="page.pageSize"
+        @handleChange="handleChangePage"
+      ></CtPagination>
+    </div>
+    <Empty v-else></Empty>
+  </div>
+</template>
+
+<script setup>
+// 感兴趣-公司收藏
+defineOptions({ name: 'company-collection' })
+import { ref } from 'vue'
+import { dealDictArrayData } from '@/views/recruit/position/components/dict'
+import { getSubscribeEnterprise } from '@/api/position'
+
+const total = ref(0)
+const items = ref([])
+const page = ref({
+  pageNo: 1,
+  pageSize: 10
+})
+
+
+// 获取公司收藏列表
+const getPositionList = async () => {
+  const { list, total: number } = await getSubscribeEnterprise(page.value)
+  items.value = dealDictArrayData([], list)
+  total.value = number
+}
+getPositionList()
+
+const handleChangePage = (index) => {
+  page.value.pageNo = index
+  getPositionList()
+}
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 52 - 0
src/views/PersonalCenter/components/delivery.vue

@@ -0,0 +1,52 @@
+<template>
+  <div>
+    <div v-if="items.length">
+      <LongStrip :items="items"></LongStrip>
+      <CtPagination
+        :total="total"
+        :page="page.pageNo"
+        :limit="page.pageSize"
+        @handleChange="handleChangePage"
+      ></CtPagination>
+    </div>
+    <Empty v-else></Empty>
+  </div>
+</template>
+
+<script setup>
+// 已投递
+defineOptions({ name: 'position-delivery' })
+import { ref } from 'vue'
+import { dealDictObjData } from '@/views/recruit/position/components/dict'
+import { getJobDeliveryList } from '@/api/position'
+
+const total = ref(0)
+const items = ref([])
+const page = ref({
+  pageNo: 1,
+  pageSize: 20
+})
+
+
+// 获取已投递职位列表
+const getPositionList = async () => {
+  const { list, total: number } = await getJobDeliveryList(page.value)
+  items.value = list.map(e => {
+    e.job = { ...e.job, ...dealDictObjData({}, e.job) }
+    e.enterprise = { ...e.enterprise, ...dealDictObjData({}, e.enterprise)}
+    e.active = false
+    return e
+  })
+  total.value = number
+}
+getPositionList()
+
+const handleChangePage = (index) => {
+  page.value.pageNo = index
+  getPositionList()
+}
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 25 - 0
src/views/PersonalCenter/components/interested.vue

@@ -0,0 +1,25 @@
+<template>
+  <div>
+    <v-divider></v-divider>
+    <v-tabs v-model="tabVal" align-tabs="start" color="primary" bg-color="#fff">
+      <v-tab :value="0">{{  $t('position.positionCollection') }}</v-tab>
+      <v-tab :value="1">{{ $t('position.companyCollection') }}</v-tab>
+    </v-tabs>
+    <div class="mt-3">
+      <component :is="tabVal === 0 ? positionCollection : companyCollection"></component>
+    </div>
+  </div>
+</template>
+
+<script setup>
+// 感兴趣
+defineOptions({ name: 'position-interested' })
+import { ref } from 'vue'
+import positionCollection from './positionCollection.vue'
+import companyCollection from './companyCollection.vue'
+
+const tabVal = ref(0)
+</script>
+
+<style scoped lang="scss">
+</style>

+ 188 - 0
src/views/PersonalCenter/components/interestedMe.vue

@@ -0,0 +1,188 @@
+<template>
+  <div>
+    <div v-if="list.length">
+      <div class="position-item mb-3 job-closed" v-for="(val, i) in list" :key="i" @mouseenter="handleMouseEnter(val)" @mouseleave="val.active = false">
+        <div class="info-header">
+          <div v-if="val.active" class="header-btn">
+            <v-btn class="half-button" color="primary" size="small">继续沟通</v-btn>
+            <v-btn v-if="props.tab === 4" class="half-button ml-3" color="primary" size="small">取消感兴趣</v-btn>
+          </div>
+          <div class="img-box">
+            <v-avatar :image="val.contact.avatars || 'https://minio.citupro.com/dev/menduner/7.png'" size="x-small"></v-avatar>
+            <span class="name">
+              <span class="mx-3">{{ val.contact.name }}</span>
+              <span class="gray">{{ val.contact.postNameCn }}</span>
+            </span>
+          </div>
+        </div>
+        <div class="info-content">
+          <div class="job-info">
+            <div class="job-name cursor-pointer">
+              <span class="mr-3 info-name">{{ val.name }}</span>
+              <span>[{{ val.areaName }}]</span>
+            </div>
+            <div class="job-other">
+              <span class="salary">{{ val.payFrom }}-{{ val.payTo }}k</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>
+          </div>
+          <div v-if="props.tab === 3" class="interview-info">
+            <div>面试时间:2022.03.15 17:00</div>
+            <div class="mt-3">面试地点:先烈中路100号大院203室</div>
+          </div>
+          <div v-else class="company-info">
+            <v-img width="50" height="50" :src="val.contact.avatars || 'https://minio.citupro.com/dev/menduner/7.png'"></v-img>
+            <div class="ml-3">
+              <div class="cursor-pointer info-name">{{ val.enterprise.name }}</div>
+              <div class="mt-3">
+                <v-chip color="primary" label size="small" class="mr-3" v-for="k in desc" :key="k">{{ val.enterprise[k] }}</v-chip>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div v-else>
+      <Empty></Empty>
+    </div>
+  </div>
+</template>
+
+<script setup>
+// 对我感兴趣
+defineOptions({ name: 'position-interested-in-me' })
+import { ref } from 'vue'
+// import { dealDictArrayData, dealDictObjData } from '@/views/recruit/position/components/dict'
+import Empty from '@/components/Empty'
+
+const props = defineProps({
+  tab: {
+    type: Number,
+    default: 0
+  }
+})
+
+// const items = ref([])
+// const page = ref({
+//   pageNo: 1,
+//   pageSize: 10
+// })
+const list = ref([
+  {
+    contact: {
+      name: '肖女士',
+      avatars: 'https://cdn.vuetifyjs.com/images/john.jpg',
+      postNameCn: '人事经理'
+    },
+    name: '前端开发工程师',
+    areaName: '北京·石景山区·八大处',
+    payFrom: 15,
+    payTo: 18,
+    expName: '3-5年',
+    eduName: '本科',
+    active: false,
+    enterprise: {
+      name: '广州辞图科技有限公司',
+      industryName: '互联网',
+      scaleName: '0-20人',
+      financingName: '未融资'
+    }
+  },
+  {
+    contact: {
+      name: '肖女士',
+      avatars: 'https://cdn.vuetifyjs.com/images/john.jpg',
+      postNameCn: '人事经理'
+    },
+    name: '前端开发工程师',
+    areaName: '北京·石景山区·八大处',
+    payFrom: 15,
+    payTo: 18,
+    expName: '3-5年',
+    eduName: '本科',
+    active: false,
+    enterprise: {
+      name: '广州辞图科技有限公司',
+      industryName: '互联网',
+      scaleName: '0-20人',
+      financingName: '未融资'
+    }
+  }
+])
+
+const desc = ['industryName', 'financingName', 'scaleName']
+
+const handleMouseEnter = (val) => {
+  if (props.tab !==3 ) val.active = true
+}
+
+</script>
+
+<style scoped lang="scss">
+.position-item {
+  height: 144px;
+  background-color: #fff;
+  border-radius: 12px;
+  &:hover {
+    box-shadow: 0 16px 40px 0 rgba(153, 153, 153, .3);
+  }
+  .info-header {
+    height: 48px;
+    background: linear-gradient(90deg,#f5fcfc,#fcfbfa);
+    .img-box {
+      padding: 12px 24px;
+      .name {
+        color: #222;
+        font-weight: 400;
+        font-size: 13px;
+        .gray {
+          color: #666;
+        }
+      }
+    }
+    .header-btn {
+      padding: 10px 10px 0 0;
+      float: right;
+      .v-btn {
+        z-index: 1;
+      }
+    }
+  }
+  .info-content {
+    display: flex;
+    padding: 16px 24px;
+    justify-content: space-between;
+    .job-info {
+      width: 430px;
+      min-width: 430px;
+      max-width: 430px;
+      font-weight: 500;
+      font-size: 16px;
+      .job-name {
+        height: 22px;
+        line-height: 22px;
+        color: #222;
+        margin-bottom: 12px;
+      }
+      .job-other {
+        color: var(--v-error-base);
+        height: 22px;
+        line-height: 22px;
+      }
+    }
+    .company-info {
+      display: flex;
+      align-items: center
+    }
+    .interview-info {
+      color: #333;
+      font-size: 15px;
+    }
+  }
+}
+
+.info-name:hover {
+  color: var(--v-primary-base);
+}
+</style>

+ 23 - 0
src/views/PersonalCenter/components/interview.vue

@@ -0,0 +1,23 @@
+<template>
+  <div>
+    <v-divider></v-divider>
+      <v-tabs v-model="tabVal" align-tabs="start" color="primary" bg-color="#fff">
+        <v-tab :value="0">待面试</v-tab>
+        <v-tab :value="1">已完成</v-tab>
+        <v-tab :value="2">未面试</v-tab>
+        <v-tab :value="3">已取消</v-tab>
+      </v-tabs>
+  </div>
+</template>
+
+<script setup>
+// 面试
+defineOptions({ name: 'formPage'})
+import { ref } from 'vue'
+
+const tabVal = ref(0)
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 52 - 0
src/views/PersonalCenter/components/positionCollection.vue

@@ -0,0 +1,52 @@
+<template>
+  <div>
+    <div v-if="items.length">
+      <LongStrip :items="items" :showCancelBtn="true" @refresh="getPositionList"></LongStrip>
+      <CtPagination
+        :total="total"
+        :page="page.pageNo"
+        :limit="page.pageSize"
+        @handleChange="handleChangePage"
+      ></CtPagination>
+    </div>
+    <Empty v-else></Empty>
+  </div>
+</template>
+
+<script setup>
+// 感兴趣-职位收藏
+defineOptions({ name: 'position-collection' })
+import { ref } from 'vue'
+import { dealDictObjData } from '@/views/recruit/position/components/dict'
+import { getJobFavoriteList } from '@/api/position'
+
+const total = ref(0)
+const items = ref([])
+const page = ref({
+  pageNo: 1,
+  pageSize: 10
+})
+
+
+// 获取收藏职位列表
+const getPositionList = async () => {
+  const { list, total: number } = await getJobFavoriteList(page.value)
+  items.value = list.map(e => {
+    e.job = { ...e.job, ...dealDictObjData({}, e.job) }
+    e.enterprise = { ...e.enterprise, ...dealDictObjData({}, e.enterprise)}
+    e.active = false
+    return e
+  })
+  total.value = number
+}
+getPositionList()
+
+const handleChangePage = (index) => {
+  page.value.pageNo = index
+  getPositionList()
+}
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 188 - 0
src/views/PersonalCenter/components/seenMe.vue

@@ -0,0 +1,188 @@
+<template>
+  <div>
+    <div v-if="list.length">
+      <div class="position-item mb-3 job-closed" v-for="(val, i) in list" :key="i" @mouseenter="handleMouseEnter(val)" @mouseleave="val.active = false">
+        <div class="info-header">
+          <div v-if="val.active" class="header-btn">
+            <v-btn class="half-button" color="primary" size="small">继续沟通</v-btn>
+            <v-btn v-if="props.tab === 4" class="half-button ml-3" color="primary" size="small">取消感兴趣</v-btn>
+          </div>
+          <div class="img-box">
+            <v-avatar :image="val.contact.avatars || 'https://minio.citupro.com/dev/menduner/7.png'" size="x-small"></v-avatar>
+            <span class="name">
+              <span class="mx-3">{{ val.contact.name }}</span>
+              <span class="gray">{{ val.contact.postNameCn }}</span>
+            </span>
+          </div>
+        </div>
+        <div class="info-content">
+          <div class="job-info">
+            <div class="job-name cursor-pointer">
+              <span class="mr-3 info-name">{{ val.name }}</span>
+              <span>[{{ val.areaName }}]</span>
+            </div>
+            <div class="job-other">
+              <span class="salary">{{ val.payFrom }}-{{ val.payTo }}k</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>
+          </div>
+          <div v-if="props.tab === 3" class="interview-info">
+            <div>面试时间:2022.03.15 17:00</div>
+            <div class="mt-3">面试地点:先烈中路100号大院203室</div>
+          </div>
+          <div v-else class="company-info">
+            <v-img width="50" height="50" :src="val.contact.avatars || 'https://minio.citupro.com/dev/menduner/7.png'"></v-img>
+            <div class="ml-3">
+              <div class="cursor-pointer info-name">{{ val.enterprise.name }}</div>
+              <div class="mt-3">
+                <v-chip color="primary" label size="small" class="mr-3" v-for="k in desc" :key="k">{{ val.enterprise[k] }}</v-chip>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div v-else>
+      <Empty></Empty>
+    </div>
+  </div>
+</template>
+
+<script setup>
+// 看过我
+defineOptions({ name: 'position-seen-me' })
+import { ref } from 'vue'
+// import { dealDictArrayData, dealDictObjData } from '@/views/recruit/position/components/dict'
+import Empty from '@/components/Empty'
+
+const props = defineProps({
+  tab: {
+    type: Number,
+    default: 0
+  }
+})
+
+// const items = ref([])
+// const page = ref({
+//   pageNo: 1,
+//   pageSize: 10
+// })
+const list = ref([
+  {
+    contact: {
+      name: '肖女士',
+      avatars: 'https://cdn.vuetifyjs.com/images/john.jpg',
+      postNameCn: '人事经理'
+    },
+    name: '前端开发工程师',
+    areaName: '北京·石景山区·八大处',
+    payFrom: 15,
+    payTo: 18,
+    expName: '3-5年',
+    eduName: '本科',
+    active: false,
+    enterprise: {
+      name: '广州辞图科技有限公司',
+      industryName: '互联网',
+      scaleName: '0-20人',
+      financingName: '未融资'
+    }
+  },
+  {
+    contact: {
+      name: '肖女士',
+      avatars: 'https://cdn.vuetifyjs.com/images/john.jpg',
+      postNameCn: '人事经理'
+    },
+    name: '前端开发工程师',
+    areaName: '北京·石景山区·八大处',
+    payFrom: 15,
+    payTo: 18,
+    expName: '3-5年',
+    eduName: '本科',
+    active: false,
+    enterprise: {
+      name: '广州辞图科技有限公司',
+      industryName: '互联网',
+      scaleName: '0-20人',
+      financingName: '未融资'
+    }
+  }
+])
+
+const desc = ['industryName', 'financingName', 'scaleName']
+
+const handleMouseEnter = (val) => {
+  if (props.tab !==3 ) val.active = true
+}
+
+</script>
+
+<style scoped lang="scss">
+.position-item {
+  height: 144px;
+  background-color: #fff;
+  border-radius: 12px;
+  &:hover {
+    box-shadow: 0 16px 40px 0 rgba(153, 153, 153, .3);
+  }
+  .info-header {
+    height: 48px;
+    background: linear-gradient(90deg,#f5fcfc,#fcfbfa);
+    .img-box {
+      padding: 12px 24px;
+      .name {
+        color: #222;
+        font-weight: 400;
+        font-size: 13px;
+        .gray {
+          color: #666;
+        }
+      }
+    }
+    .header-btn {
+      padding: 10px 10px 0 0;
+      float: right;
+      .v-btn {
+        z-index: 1;
+      }
+    }
+  }
+  .info-content {
+    display: flex;
+    padding: 16px 24px;
+    justify-content: space-between;
+    .job-info {
+      width: 430px;
+      min-width: 430px;
+      max-width: 430px;
+      font-weight: 500;
+      font-size: 16px;
+      .job-name {
+        height: 22px;
+        line-height: 22px;
+        color: #222;
+        margin-bottom: 12px;
+      }
+      .job-other {
+        color: var(--v-error-base);
+        height: 22px;
+        line-height: 22px;
+      }
+    }
+    .company-info {
+      display: flex;
+      align-items: center
+    }
+    .interview-info {
+      color: #333;
+      font-size: 15px;
+    }
+  }
+}
+
+.info-name:hover {
+  color: var(--v-primary-base);
+}
+</style>

+ 21 - 19
src/views/PersonalCenter/dynamic/left.vue

@@ -50,23 +50,13 @@
       </div>
       </div>
     </div>
     </div>
 
 
-    <div class="left-tabs my-3">
-      <v-tabs v-model="tab" align-tabs="start" color="primary" bg-color="#fff" @update:model-value="getPositionList">
-        <v-tab :value="1">{{ $t('position.throughCommunication') }}</v-tab>
-        <v-tab :value="2">{{ $t('position.delivered') }}</v-tab>
-        <v-tab :value="3">{{ $t('position.interview') }}</v-tab>
-        <v-tab :value="4">{{ $t('position.interested') }}</v-tab>
-        <v-tab :value="5">{{ $t('position.interestedInMe') }}</v-tab>
-        <v-tab :value="6">{{ $t('position.haveSeenMe') }}</v-tab>
-      </v-tabs>
-      <v-divider></v-divider>
-      <v-tabs v-if="tab === 4" v-model="secondTab" align-tabs="start" color="primary" bg-color="#fff">
-        <v-tab :value="7">{{  $t('position.positionCollection') }}</v-tab>
-        <v-tab :value="8">{{ $t('position.companyCollection') }}</v-tab>
+    <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>
       </v-tabs>
     </div>
     </div>
-    <div class="left-bottom">
-      <PositionItem :tab="tab"></PositionItem>
+    <div :class="['left-bottom', {'mt-3': list[tab].path !== interview && list[tab].path !== interested}]">
+      <component :is="list[tab].path"></component>
     </div>
     </div>
   </div>
   </div>
 </template>
 </template>
@@ -77,11 +67,25 @@ import { getDict } from '@/hooks/web/useDictionaries'
 import { ref } from 'vue'
 import { ref } from 'vue'
 import { useUserStore } from '@/store/user'
 import { useUserStore } from '@/store/user'
 import { updateJobStatus } from '@/api/resume'
 import { updateJobStatus } from '@/api/resume'
-import PositionItem from '../components/posiitonItem.vue'
+import { useI18n } from '@/hooks/web/useI18n'
 import Snackbar from '@/plugins/snackbar'
 import Snackbar from '@/plugins/snackbar'
+import communication from '../components/communication.vue'
+import delivery from '../components/delivery.vue'
+import interview from '../components/interview.vue'
+import interested from '../components/interested.vue'
+import interestedMe from '../components/interestedMe.vue'
+import seenMe from '../components/seenMe.vue'
 
 
+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.interestedInMe'), path: interestedMe },
+  { title: t('position.haveSeenMe'), path: seenMe }
+]
 const tab = ref(1)
 const tab = ref(1)
-const secondTab = ref(7)
 const selectVal = ref('0')
 const selectVal = ref('0')
 const items = ref([])
 const items = ref([])
 const userStore = useUserStore()
 const userStore = useUserStore()
@@ -106,8 +110,6 @@ const handleChangeJobStatus = async () => {
   await userStore.getUserBaseInfos(baseInfo.value.userId)
   await userStore.getUserBaseInfos(baseInfo.value.userId)
   getBasicInfo()
   getBasicInfo()
 }
 }
-
-const getPositionList = () => {}
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">

+ 10 - 11
src/views/recruit/position/components/dict.js

@@ -33,18 +33,17 @@ export const getData = async () => {
 getData()
 getData()
 
 
 export const dealDictArrayData = (res, list) => {
 export const dealDictArrayData = (res, list) => {
-  dictList.value.forEach(item => {
-    const valueKey = item.nameKey ? item.nameKey : 'label'
-    const idKey = item.valueKey ? item.valueKey : 'value'
-    const next = list.find(val => val[item?.key])
-    if (!next) return
-    res = list.map(e => {
-      const obj = dictObj[item.value].find(k => Number(k[idKey]) === Number(e[item.key]))
-      if (!obj) return
-      e[item.label] = obj[valueKey] || ''
-      e.active = false
-      return e
+  res = list.map(item => {
+    Object.keys(item).map(e => {
+      const data = dictList.value.find(k => k.key === e)
+      if (!data) return
+      const valueKey = data.nameKey ? data.nameKey : 'label'
+      const idKey = data.valueKey ? data.valueKey : 'value'
+      const result = dictObj[data.value].find(val => val[idKey] === item[e])
+      if (!result) return
+      item[data.label] = result[valueKey] || ''
     })
     })
+    return { ...item, active: false }
   })
   })
   return res
   return res
 }
 }