Explorar o código

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

zhengnaiwen_citu hai 7 meses
pai
achega
3b3c318ccb
Modificáronse 24 ficheiros con 457 adicións e 129 borrados
  1. 3 0
      src/components/DatePicker/index.vue
  2. 3 0
      src/components/FormUI/datePicker/index copy.vue
  3. 3 0
      src/components/FormUI/datePicker/index.vue
  4. 13 18
      src/components/jobTypeCard/index.vue
  5. 4 0
      src/hooks/web/useDictionaries.js
  6. 2 1
      src/views/common/loginDialog.vue
  7. 5 1
      src/views/recruit/enterprise/interviewManagement/components/invite.vue
  8. 5 1
      src/views/recruit/enterprise/personnelManagement/components/invite.vue
  9. 8 2
      src/views/recruit/enterprise/resume/components/invite.vue
  10. 5 1
      src/views/recruit/enterprise/resume/components/public.vue
  11. 33 0
      src/views/recruit/enterprise/search/components/common.vue
  12. 5 1
      src/views/recruit/enterprise/search/index.vue
  13. 2 2
      src/views/recruit/enterprise/search/recommend/index.vue
  14. 37 0
      src/views/recruit/enterprise/search/retrieval/components/area.vue
  15. 68 0
      src/views/recruit/enterprise/search/retrieval/components/industry.vue
  16. 54 0
      src/views/recruit/enterprise/search/retrieval/index.vue
  17. 17 21
      src/views/recruit/personal/home/components/homeJobTypeCard/index.vue
  18. 2 1
      src/views/recruit/personal/position/components/conditionFilter.vue
  19. 10 6
      src/views/recruit/personal/position/components/conditionFilter/commonPath.vue
  20. 18 4
      src/views/recruit/personal/position/components/details.vue
  21. 33 23
      src/views/recruit/personal/position/components/dict.js
  22. 1 0
      src/views/recruit/personal/position/index.vue
  23. 38 24
      src/views/recruit/personal/recommend/components/item.vue
  24. 88 23
      src/views/recruit/personal/recommend/index.vue

+ 3 - 0
src/components/DatePicker/index.vue

@@ -18,6 +18,9 @@
       :clearable="options.clearable ?? true"
       :day-names="['一', '二', '三', '四', '五', '六', '七']"
       v-bind="$attrs"
+      :time-picker-inline="true"
+      select-text="确认"
+      cancel-text="取消"
       @update:model-value="options?.change"
     ></VueDatePicker>
   </div>

+ 3 - 0
src/components/FormUI/datePicker/index copy.vue

@@ -20,6 +20,9 @@
         :clearable="item.clearable ?? true"
         :day-names="['一', '二', '三', '四', '五', '六', '七']"
         v-bind="$attrs"
+        :time-picker-inline="true"
+        select-text="确认"
+        cancel-text="取消"
         :class="{'detailMargin': detailMargin}"
         style="flex: 1"
         @open="handleOpen"

+ 3 - 0
src/components/FormUI/datePicker/index.vue

@@ -14,11 +14,14 @@
         :year-picker="year"
         auto-apply
         text-input
+        :time-picker-inline="true"
         :show-now-button="item.showToday"
         now-button-label="今天"
         :enable-time-picker="item.enableTimePicker ?? false"
         :clearable="item.clearable ?? true"
         :day-names="['一', '二', '三', '四', '五', '六', '七']"
+        select-text="确认"
+        cancel-text="取消"
         v-bind="$attrs"
         :class="{'detailMargin': detailMargin}"
         style="flex: 1"

+ 13 - 18
src/components/jobTypeCard/index.vue

@@ -1,4 +1,3 @@
-<!-- 三级结构 -->
 <template>
   <div class="floatCard d-flex" style="z-index: 999" @mouseleave="handleMouseLeave">
     <v-card class="card">
@@ -19,23 +18,19 @@
     </v-card>
     <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">
-          <v-divider v-if="index" class="divider"></v-divider>
-          <div class="rowItem d-flex">
-            <div class="categoryName2">{{ item.nameCn }}</div>
-            <div class="rightContent">
-              <div v-if="!item.children?.length"></div>
-              <div 
-                v-else 
-                :class="['jobItem', {'active': selectItems.includes(val.id)}]"
-                v-for="val in item.children" 
-                :key="val.id" 
-                @click="handleClick(val)"
-              >
-              {{ val.nameCn }}</div>
-            </div>
-          </div>
+        <div class="rightContent">
+          <div
+            v-if="!rightObj.data.children?.length"
+            style="width: 100%; text-align: center; color: gray; margin-top: 100px;"
+          >暂无数据</div>
+          <div 
+            v-else 
+            :class="['jobItem', {'active': selectItems.includes(val.id)}]"
+            v-for="val in rightObj.data.children" 
+            :key="val.id" 
+            @click="handleClick(val)"
+          >
+          {{ val.nameCn }}</div>
         </div>
       </div>
     </v-card>

+ 4 - 0
src/hooks/web/useDictionaries.js

@@ -15,6 +15,10 @@ import {
 // }
 
 const setDict = (type, val, cacheTime = 7200) => {
+  if (type === 'areaTreeData') {
+    const obj = val.find(e => e.name === '中国')
+    val = obj?.children ? obj.children : []
+  }
   localStorage.setItem(type, JSON.stringify({
     data: val,
     expire: Date.now() + cacheTime * 1000

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

@@ -13,6 +13,7 @@
       <v-btn :loading="loginLoading" color="primary" class="white--text mt-5" min-width="350" @click="handleLogin">
         {{ $t('login.loginOrRegister') }}
       </v-btn>
+      <div class="text-center color-666 font-size-14 mt-3">未注册的手机号,验证后自动注册账号</div>
     </div>
   </CtDialog>
 </template>
@@ -49,7 +50,7 @@ const handleLogin = async () => {
   }
   loginLoading.value = true
   try {
-    const params = { ...phoneRef.value.loginData } // 只能验证码登录
+    const params = { ...phoneRef.value.loginData, autoRegister: true } // autoRegister: 是否自动注册
     await userStore.handleSmsLogin(params)
     emit('loginSuccess')
   } catch (error) {

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

@@ -1,5 +1,5 @@
 <template>
-  <CtForm ref="CtFormRef" :items="formItems" style="height: 500px;">
+  <CtForm ref="CtFormRef" :items="formItems" style="height: 520px;">
     <template #time="{ item }">
       <VueDatePicker 
         v-model="item.value"
@@ -8,6 +8,10 @@
         model-type="timestamp"
         :disabled-dates="disabledDates"
         :day-names="['一', '二', '三', '四', '五', '六', '七']"
+        select-text="确认"
+        cancel-text="取消"
+        :time-picker-inline="true"
+        locale="zh-CN"
         :text-input="{ format: 'MM.dd.yyyy HH:mm' }" />
     </template>
   </CtForm>

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

@@ -1,5 +1,5 @@
 <template>
-  <CtForm ref="CtFormRef" :items="formItems" style="height: 500px;">
+  <CtForm ref="CtFormRef" :items="formItems" style="height: 520px;">
     <template #time="{ item }">
       <VueDatePicker 
         v-model="item.value"
@@ -8,6 +8,10 @@
         model-type="timestamp"
         :disabled-dates="disabledDates"
         :day-names="['一', '二', '三', '四', '五', '六', '七']"
+        select-text="确认"
+        cancel-text="取消"
+        :time-picker-inline="true"
+        locale="zh-CN"
         :text-input="{ format: 'MM.dd.yyyy HH:mm' }" />
     </template>
   </CtForm>

+ 8 - 2
src/views/recruit/enterprise/resume/components/invite.vue

@@ -1,14 +1,20 @@
 <template>
-  <CtForm ref="CtFormRef" :items="formItems" style="height: 500px;">
+  <CtForm ref="CtFormRef" :items="formItems" style="height: 520px;">
     <template #time="{ item }">
       <VueDatePicker 
         v-model="item.value"
         placeholder="面试时间 *"
         class="mb-4"
         model-type="timestamp"
+        :time-picker-inline="true"
         :disabled-dates="disabledDates"
         :day-names="['一', '二', '三', '四', '五', '六', '七']"
-        :text-input="{ format: 'MM.dd.yyyy HH:mm' }" />
+        select-text="确认"
+        cancel-text="取消"
+        locale="zh-CN"
+        :text-input="{ format: 'MM.dd.yyyy HH:mm' }"
+      >
+      </VueDatePicker>
     </template>
   </CtForm>
 </template>

+ 5 - 1
src/views/recruit/enterprise/resume/components/public.vue

@@ -1,11 +1,15 @@
 <template>
-  <CtForm ref="CtFormRef" :items="formItems" style="height: 500px;">
+  <CtForm ref="CtFormRef" :items="formItems" style="height: 520px;">
     <template #time="{ item }">
       <VueDatePicker 
         v-model="item.value"
         placeholder="面试时间 *"
         class="mb-4"
         model-type="timestamp"
+        select-text="确认"
+        cancel-text="取消"
+        :time-picker-inline="true"
+        locale="zh-CN"
         :text-input="{ format: 'MM.dd.yyyy HH:mm' }" />
     </template>
   </CtForm>

+ 33 - 0
src/views/recruit/enterprise/search/components/common.vue

@@ -0,0 +1,33 @@
+<template>
+  <div class="d-flex align-center color-666">
+    <span class="font-size-14">{{ title }}:</span>
+    <v-chip-group v-model="select" selected-class="text-primary" mandatory @update:modelValue="handleSelect">
+      <v-chip v-for="val in items" :key="val.id" :text="val.label" :value="val.value" label filter size="small"></v-chip>
+    </v-chip-group>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'search-common'})
+import { ref } from 'vue'
+import { getDict } from '@/hooks/web/useDictionaries'
+
+const emit = defineEmits(['select'])
+const props = defineProps({
+  dictType: String,
+  title: String
+})
+
+const select = ref(-1)
+const items = ref([])
+getDict(props.dictType).then(({ data }) => {
+  items.value = [{ value: -1, label: '不限' }, ...data] || []
+})
+
+const handleSelect = (val) => {
+  emit('select', val)
+}
+</script>
+
+<style scoped lang="scss">
+</style>

+ 5 - 1
src/views/recruit/enterprise/search/index.vue

@@ -4,14 +4,18 @@
       <v-tab :value="0">推荐</v-tab>
       <v-tab :value="1">检索</v-tab>
     </v-tabs>
+    <searchRecommend v-if="tab === 0"></searchRecommend>
+    <searchRetrieval v-else></searchRetrieval>
   </v-card>
 </template>
 
 <script setup>
 defineOptions({ name: 'enterprise-personal-search'})
 import { ref } from 'vue'
+import searchRecommend from './recommend/index.vue'
+import searchRetrieval from './retrieval/index.vue'
 
-const tab = ref(0)
+const tab = ref(1)
 </script>
 
 <style scoped lang="scss">

+ 2 - 2
src/views/recruit/personal/recommend/components/details.vue → src/views/recruit/enterprise/search/recommend/index.vue

@@ -1,9 +1,9 @@
 <template>
-  <div>xxx</div>
+  <div>searchRecommend</div>
 </template>
 
 <script setup>
-defineOptions({ name: 'recommendDetails'})
+defineOptions({ name: 'searchRecommend' })
 </script>
 
 <style scoped lang="scss">

+ 37 - 0
src/views/recruit/enterprise/search/retrieval/components/area.vue

@@ -0,0 +1,37 @@
+<template>
+  <div class="font-size-15 d-flex align-center color-666">
+    <span style="display: block; width: 71px; text-align: end;">地区:</span>
+    <v-menu :close-delay="1" :open-delay="0" v-bind="$attrs" :close-on-content-click="true">
+      <template v-slot:activator="{  props }">
+        <div>
+          <v-chip v-for="k in areaSelect" :key="k.id" class="mr-3" closable label size="small" @click:close="handleAreaClear(k)">{{ k.name }}</v-chip>
+          <v-btn icon="mdi-plus" v-bind="props" variant="outlined" size="x-small"></v-btn>
+        </div>
+      </template>
+      <AreaSelect :select="select" :currentData="areaSelect" :limit="1" @handleClick="handleArea"></AreaSelect>
+    </v-menu>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'search-retrieval-area' })
+import { ref } from 'vue'
+
+const select = ref([])
+const areaSelect = ref([])
+
+const handleArea = (list, arr) => {
+  select.value = list
+  areaSelect.value = arr
+}
+
+const handleAreaClear = (k) => {
+  select.value = select.value.filter(item => item !== k.id)
+  const index = areaSelect.value.findIndex(item => item.id === k.id)
+  if (index !== -1) areaSelect.value.splice(index, 1)
+}
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 68 - 0
src/views/recruit/enterprise/search/retrieval/components/industry.vue

@@ -0,0 +1,68 @@
+<template>
+  <div class="d-flex font-size-14">
+    <div v-for="val in items" :key="val.id" class="mr-5 cursor-pointer" :class="{'active': val.active}" @click="handleFirst(val)">{{ val.nameCn }}</div>
+  </div>
+  <div class="mt-5 font-size-14">
+    <span 
+      v-for="k in children" 
+      :key="k.id" 
+      class="d-inline-block mr-10 cursor-pointer color-666 mb-2" 
+      :class="{'active': select.findIndex(e => e.id === k.id) !== -1}"
+      @click="handleSecond(k)"
+    >{{ k.nameCn }}</span>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'search-screen-industry'})
+import { ref, watch } from 'vue'
+import { getDict } from '@/hooks/web/useDictionaries'
+
+const emit = defineEmits(['select'])
+const props = defineProps({
+  selectData: {
+    type: Array,
+    default: () => []
+  }
+})
+
+const items = ref([])
+const select = ref([])
+const children = ref([])
+getDict('positionTreeData', {}, 'positionTreeData').then(({ data }) => {
+  if (!data) return
+  items.value = data.map(e => {
+    e.active = false
+    return e
+  })
+})
+
+watch(
+  () => props.selectData,
+  (val) => {
+    select.value = val
+  },
+  { deep: true }
+)
+
+const handleFirst = (val) => {
+  items.value.forEach(e => e.active = false)
+  val.active = !val.active
+  children.value = val.children || []
+}
+
+const handleSecond = (val) => {
+  const index = select.value.findIndex(e => e.id === val.id)
+  if (index !== -1) {
+    select.value.splice(index, 1)
+  } else select.value.push(val)
+  emit('select', select.value)
+}
+</script>
+
+<style scoped lang="scss">
+.active {
+  color: var(--v-primary-base);
+  font-weight: bold;
+}
+</style>

+ 54 - 0
src/views/recruit/enterprise/search/retrieval/index.vue

@@ -0,0 +1,54 @@
+<template>
+  <div class="mt-3">
+    <div class="d-flex justify-center">
+      <TextInput :item="textItem" @enter="val => handleSearch('content', val)" @appendInnerClick="val => handleSearch('content', val)"></TextInput>
+    </div>
+    <Industry :selectData="industry" @select="val => industry = val"></Industry>
+    <Area></Area>
+    <CommonPage class="my-3" dictType="menduner_education_type" title="最高学历" @select="val => handleSearch('eduType', val)"></CommonPage>
+    <CommonPage dictType="menduner_exp_type" title="工作经验" @select="val => handleSearch('expType', val)"></CommonPage>
+    <v-divider class="mt-1 mb-3"></v-divider>
+    <div>
+      <div>
+        <v-chip v-for="k in industry" :key="k.id" label class="mr-3" closable @click:close="handleClose(k)">{{ k.nameCn }}</v-chip>
+      </div>
+      <div v-if="industry.length" class="text-end font-size-15 color-999 cursor-pointer tips" @click="handleClear">清除选择</div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'searchRetrieval' })
+import { ref } from 'vue'
+import CommonPage from '../components/common.vue'
+import Industry from './components/industry.vue'
+import Area from './components/area.vue';
+
+const textItem = ref({
+  type: 'text',
+  width: 600,
+  value: '',
+  label: '输入关键字',
+  clearable: true,
+  appendInnerIcon: 'mdi-magnify'
+})
+
+const industry = ref([])
+const handleSearch = (key, value) => {
+  console.log(key, value, 'search')
+}
+
+const handleClose = (item) => {
+  industry.value = industry.value.filter(k => k.id !== item.id)
+}
+
+const handleClear = () => {
+  industry.value = []
+}
+</script>
+
+<style scoped lang="scss">
+.tips:hover {
+  color: var(--v-primary-base)
+}
+</style>

+ 17 - 21
src/views/recruit/personal/home/components/homeJobTypeCard/index.vue

@@ -9,12 +9,12 @@
           @mouseover="handleMouseOver(item, index)"
         >
           <div class="rowItem d-flex">
-            <span class="categoryName">{{ item.nameCn }}</span>
-            <div class="jobItemsBox">
+            <div class="categoryName" style="width: 100%; text-align: center; cursor: pointer;">{{ item.nameCn }}</div>
+            <!-- <div class="jobItemsBox">
               <div class="outerCovering" v-if="item.children?.length && item.children[0].children.length">
                 <div class="jobItems" v-for="val in item.children[0].children" :key="val.id" @click="handleJobClick(val)">{{ val.nameCn }}</div>
               </div>
-            </div>
+            </div> -->
             <span class="mdi mdi-menu-right"></span>
           </div>
         </div>
@@ -34,23 +34,19 @@
     </v-card>
     <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">
-          <v-divider v-if="index" class="divider"></v-divider>
-          <div class="rowItem d-flex">
-            <div class="categoryName2">{{ item.nameCn }}</div>
-            <div class="rightContent">
-              <div v-if="!item.children?.length"></div>
-              <div 
-                v-else 
-                class="jobItem"
-                v-for="val in item.children" 
-                :key="val.id" 
-                @click="handleJobClick(val)"
-              >
-              {{ val.nameCn }}</div>
-            </div>
-          </div>
+        <div class="rightContent">
+          <div
+            v-if="!rightObj.data.children?.length"
+            style="width: 100%; text-align: center; color: gray; margin-top: 100px;"
+          >暂无数据</div>
+          <div 
+            v-else 
+            class="jobItem"
+            v-for="val in rightObj.data.children" 
+            :key="val.id" 
+            @click="handleJobClick(val)"
+          >
+          {{ val.nameCn }}</div>
         </div>
       </div>
     </v-card>
@@ -155,7 +151,7 @@ const carouselList = ref([
   justify-content: center; // 后面的flex会继承
   .leftCard {
     height: 384px;
-    width: 380px;
+    width: 300px;
     margin: 4px 0;
     overflow-x: hidden;
     overflow-y: auto;

+ 2 - 1
src/views/recruit/personal/position/components/conditionFilter.vue

@@ -7,7 +7,8 @@
           :idName="item.key"
           :title="item.title"
           :isSingle="item.isSingle"
-          :dictName="item.dictShow"
+          :isSlot="item.isSlot"
+          :displayDictName="item.displayDictName"
           :provideData="item.provideData || []"
           :info="item"
           @inputChange="inputChange"

+ 10 - 6
src/views/recruit/personal/position/components/conditionFilter/commonPath.vue

@@ -33,6 +33,10 @@ const props = defineProps({
     type: Boolean,
     default: false
   },
+  isSlot: { // 使用插槽(不调用getDict)
+    type: Boolean,
+    default: false
+  },
   isSingle: { // 是否单选
     type: Boolean,
     default: false
@@ -41,9 +45,9 @@ const props = defineProps({
     type: String,
     default: ''
   },
-  dictName: {
+  displayDictName: {
     type: [String, Number],
-    default: ''
+    default: '' // 1: 插槽使用会返回数组
   },
   provideData: {
     type: Array,
@@ -64,7 +68,7 @@ let items = ref()
 const selectedItems = ref([])
 
 const handle = (value) => {
-  if (props.dictName === 1) { // 插槽使用会返回数组
+  if (props.isSlot) {
     selectedItems.value = value
   } else {
     if (selectedItems.value.includes(value)) {
@@ -84,13 +88,13 @@ const handle = (value) => {
 }
 
 // 字典
-if (props.dictName === 1) show.value = true // 插槽使用
+if (props.isSlot) show.value = true // 插槽使用
 else if (props.provideData?.length) { // 自定义下拉数据
   items.value = props.provideData
   show.value = true
 }
-else if (props.dictName) {
-  getDict(props.dictName).then(({ data }) => {
+else if (props.displayDictName) {
+  getDict(props.displayDictName).then(({ data }) => {
     data = data?.length && data || []
     items.value = data
     show.value = true

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

@@ -1,6 +1,6 @@
 <template>
   <div style="position: relative;">
-    <div class="default-width banner px-6" id="share">
+    <div class="banner px-6" id="share" :class="{'default-width': defaultWidth}">
       <div class="banner-title d-flex justify-space-between">
         <div class="d-flex align-center">
           <h1 class="ellipsis">{{ info.name }}</h1>
@@ -89,7 +89,7 @@
               <v-btn class="radius button-item" :disabled="delivery" color="primary" @click="handleDelivery">{{ delivery ? $t('position.delivered') : $t('position.submitResume') }}</v-btn>
             </div>
           </div>
-          <div class="content-right" v-if="Object.keys(info).length">
+          <div class="content-right" v-if="Object.keys(info).length && props.showContentRight">
             <!-- 公司信息 -->
             <EnterpriseInfo :info="{ ...info, position: { ...positionInfo } }"></EnterpriseInfo>
             <!-- 相似职位 -->
@@ -171,6 +171,20 @@ 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'
+const props = defineProps({
+  defaultWidth: {
+    type: Boolean,
+    default: true
+  },
+  showContentRight: {
+    type: Boolean,
+    default: true
+  },
+  propJobId: {
+    type: String,
+    default: ''
+  },
+})
 
 import {
   getPositionDetails,
@@ -191,7 +205,7 @@ import { getUserAvatar } from '@/utils/avatar'
 
 const { t } = useI18n()
 const router = useRouter()
-const { id } = router.currentRoute.value.params
+const { id } = props.propJobId ? { id: props.propJobId } : router.currentRoute.value.params
 const delivery = ref(false) // 是否已投递简历
 const loading = ref(false)
 const showLogin = ref(false)
@@ -279,7 +293,7 @@ const getPositionDetail = async () => {
   const data = await getPositionDetails({ id })
   info.value = data
   positionInfo.value = { ...dealDictObjData({}, info.value), ...info.value }
-  getSimilarPositionList()
+  if (props.type !=='recommendShow') getSimilarPositionList()
 }
 getPositionDetail()
 

+ 33 - 23
src/views/recruit/personal/position/components/dict.js

@@ -2,11 +2,11 @@ import { getDict } from '@/hooks/web/useDictionaries'
 import commonPath from './conditionFilter/commonPath.vue'
 // import areaType from './conditionFilter/areaType.vue'
 
-// dictUse: 查数据回显(一维数组字典),dictShow: 用户操作的字典(用户看到的数据)
+// toFilterDictName: 查数据回显(一维数组字典),dictShow: 用户操作的字典(用户看到的数据)
 // 当type是tree类型的数据的时候需要提供dictType
 const dictList = [
-  { 
-    dictShow: 'menduner_area_type',
+  {
+    displayDictName: 'menduner_area_type',
     apiType: 'areaList',
     key: 'areaIds',
     itemKey: 'id',
@@ -15,22 +15,32 @@ const dictList = [
     path: commonPath,
     data: []
   },
-  { 
-    dictUse: 'menduner_industry_type',
+  {
+    displayDictName: 'menduner_area_type',
+    apiType: 'areaList',
+    key: 'areaIds',
+    itemKey: 'id',
+    itemText: 'name',
+    title: '工作地点',
+    path: commonPath,
+    data: []
+  },
+  {
+    toFilterDictName: 'menduner_industry_type',
     apiType: 'industryList',
-    dictShow: 1,
     key: 'industryIds',
+    isSlot: true,
     itemKey: 'id',
     itemText: 'nameCn',
     title: '行业类型',
     path: commonPath,
     data: []
   },
-  { 
-    dictUse: 'positionData',
+  {
+    toFilterDictName: 'positionData',
     apiType: 'positionData',
-    dictShow: 1,
     key: 'positionId',
+    isSlot: true,
     itemKey: 'id',
     itemText: 'nameCn',
     title: '职位类型',
@@ -38,8 +48,8 @@ const dictList = [
     path: commonPath,
     data: []
   },
-  { 
-    dictShow: 'menduner_job_type',
+  {
+    displayDictName: 'menduner_job_type',
     key: 'jobType',
     itemKey: 'value',
     itemText: 'label',
@@ -47,8 +57,8 @@ const dictList = [
     path: commonPath,
     data: []
   },
-  { 
-    dictShow: 'menduner_exp_type',
+  {
+    displayDictName: 'menduner_exp_type',
     key: 'expType',
     itemKey: 'value',
     itemText: 'label',
@@ -56,8 +66,8 @@ const dictList = [
     path: commonPath,
     data: []
   },
-  { 
-    dictShow: 'menduner_pay_scope',
+  {
+    displayDictName: 'menduner_pay_scope',
     key: 'payScope',
     itemKey: 'value',
     itemText: 'label',
@@ -67,8 +77,8 @@ const dictList = [
     path: commonPath,
     data: []
   },
-  { 
-    dictShow: 'menduner_education_type',
+  {
+    displayDictName: 'menduner_education_type',
     key: 'eduType',
     itemKey: 'value',
     itemText: 'label',
@@ -76,8 +86,8 @@ const dictList = [
     path: commonPath,
     data: []
   },
-  { 
-    dictShow: 'menduner_scale',
+  {
+    displayDictName: 'menduner_scale',
     key: 'scale',
     itemKey: 'value',
     itemText: 'label',
@@ -85,8 +95,8 @@ const dictList = [
     path: commonPath,
     data: []
   },
-  { 
-    dictShow: 'menduner_financing_status',
+  {
+    displayDictName: 'menduner_financing_status',
     key: 'financingStatus',
     itemKey: 'value',
     itemText: 'label',
@@ -100,8 +110,8 @@ export const filterList = dictList
 // 字典
 const getDictList = async () => {
   dictList.forEach(async (dictListItem) => {
-    const dictUse = dictListItem.dictUse || dictListItem.dictShow
-    const { data } = await getDict(dictUse, dictListItem.params, dictListItem.apiType)
+    const toFilterDictName = dictListItem.toFilterDictName || dictListItem.displayDictName
+    const { data } = await getDict(toFilterDictName, dictListItem.params, dictListItem.apiType)
     dictListItem.data = data
   })
 }

+ 1 - 0
src/views/recruit/personal/position/index.vue

@@ -49,6 +49,7 @@ const route = useRoute(); const router = useRouter()
 const cityFilterRef = ref()
 const conditionFilterRef = ref()
 const showFilterList = [
+  // { key: 'cityId' },
   { key: 'industryIds' },
   { key: 'positionId', isSingle: true },
   { key: 'jobType' },

+ 38 - 24
src/views/recruit/personal/recommend/components/item.vue

@@ -1,40 +1,46 @@
 <template>
   <div class="d-flex">
     <div class="position-box">
-      <div class="sub-li" v-for="(item, index) in list" :key="index">
-        <div class="job-info" @click="handlePosition(item)" @mouseenter="item.active = true" @mouseleave="item.active = false">
+      <div class="sub-li"
+        v-for="(item, index) in list" :key="index"
+        :class="{'chosen': chosenIndex === index}"
+        :style="`margin-top: ${index ? '12px' : '0'}`"
+        @mouseenter="item.active = true" @mouseleave="item.active = false"
+        @click="handleClick(item, index)"
+      >
+        <div class="job-info">
           <div class="sub-li-top">
             <div class="sub-li-info">
-              <p :class="['name', {'default-active': item.active }]">{{ item.job.name }}</p>
-              <svg-icon v-if="item.job.hire" name="pin" size="30"></svg-icon>
+              <p :class="['name', {'default-active': item.active }]">{{ item.name }}</p>
+              <svg-icon v-if="item.hire" name="pin" size="30"></svg-icon>
             </div>
-            <p v-if="!item.job.payFrom && !item.job.payTo" class="salary">面议</p>
-            <p v-else class="salary">{{ item.job.payFrom ? item.job.payFrom + '-' : '' }}{{ item.job.payTo }}{{ item.job.payName ? '/' + item.job.payName : '' }}</p>
+            <p v-if="!item.payFrom && !item.payTo" class="salary">面议</p>
+            <p v-else class="salary">{{ item.payFrom ? item.payFrom + '-' : '' }}{{ item.payTo }}{{ item.payName ? '/' + item.payName : '' }}</p>
           </div>
           <div class="d-flex justify-space-between align-center">
             <div>
               <span v-for="(j, i) in desc" :key="i" class="font-size-13" style="color: #808080;">
-                <span v-if="item.job[j.value]" class="mr-1 d-inline-block">{{ item.job[j.value] }}</span>
-                <span v-if="i !== desc.length - 1 && item.job[j.value] && item.job[desc[i + 1].value]" class="septal-line ml-1"></span>
+                <span v-if="item[j.value]" class="mr-1 d-inline-block">{{ item[j.value] }}</span>
+                <span v-if="i !== desc.length - 1 && item[j.value] && item[desc[i + 1].value]" class="septal-line ml-1"></span>
               </span>
             </div>
           </div>
           <div class="ellipsis" style="height: 24px;overflow: hidden;">
-            <span v-for="(j, i) in item.job.tagList" :key="i" class="mr-3 tags" style="color: #345768;">{{ j }}</span>
+            <span v-for="(j, i) in item.tagList" :key="i" class="mr-3 tags" style="color: #345768;">{{ j }}</span>
           </div>
         </div>
-        <div class="sub-li-bottom" @click="handleEnterprise(item)">
+        <div class="sub-li-bottom">
           <div class="user-info">
             <div class="d-flex align-center">
               <v-avatar size="35">
-                <v-img :src="item.enterprise.logoUrl || 'https://minio.citupro.com/dev/menduner/company-avatar.png'" />
+                <v-img :src="item.logoUrl || 'https://minio.citupro.com/dev/menduner/company-avatar.png'" />
               </v-avatar>
               <span class="names ml-2 font-size-14 ellipsis" style="max-width: 88%;">
-                {{ item.enterprise.anotherName }}
+                {{ item.anotherName }}
                 <span class="color-999 font-size-13 ml-3">
-                  <span>{{ item.enterprise.industryName }}</span>
-                  <span class="septal-line" v-if="item.enterprise.industryName && item.enterprise.scaleName"></span>
-                  <span>{{ item.enterprise.scaleName }}</span>
+                  <span>{{ item.industryName }}</span>
+                  <span class="septal-line" v-if="item.industryName && item.scaleName"></span>
+                  <span>{{ item.scaleName }}</span>
                 </span>
               </span>
             </div>
@@ -49,12 +55,14 @@
 defineOptions({ name: 'position-card-item' })
 import { ref, watch } from 'vue'
 
+const emit = defineEmits([''])
 const props = defineProps({
   items: {
     type: Array,
     default: () => []
   }
 })
+const chosenIndex = ref(0)
 const list = ref([])
 watch(
   () => props.items, 
@@ -70,12 +78,14 @@ const desc = [
   { mdi: 'mdi-clock-time-ten-outline', value: 'expName' }
 ]
 
-const handlePosition = (item) => {
-  console.log(item, 'iiiiiiii')
-}
-const handleEnterprise = (item) => {
-  window.open(`/recruit/personal/company/details/${item.enterprise.id}?key=briefIntroduction`)
+const handleClick = (item, index) => {
+  chosenIndex.value = index
+  list.value.forEach((e, i) => e.active = i === index )
+  emit('selectChange', item)
 }
+// const handleEnterprise = (item) => {
+//   window.open(`/recruit/personal/company/details/${item.id}?key=briefIntroduction`)
+// }
 </script>
 
 <style lang="scss" scoped>
@@ -83,19 +93,23 @@ const handleEnterprise = (item) => {
   width: 100%;
   height: 100%;
 }
+.chosen { border: 1px solid var(--v-primary-lighten2) !important; }
 .sub-li {
   position: relative;
   width: 384px;
   height: 149px;
-  margin-bottom: 12px;
+  margin-right: 8px;
+  margin-top: 12px;
   border-radius: 12px;
   padding: 0;
   overflow: hidden;
   cursor: pointer;
-  transition: all .2s linear;
+  // 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-primary-base) !important; }
   }
 }
 .job-info {
@@ -170,9 +184,9 @@ const handleEnterprise = (item) => {
 }
 .names {
   font-weight: 500;
-  color: #404040;
   &:hover {
-    color: var(--v-primary-base);
+    // color: var(--v-primary-base);
+    color: #404040;
   }
 }
 </style>

+ 88 - 23
src/views/recruit/personal/recommend/index.vue

@@ -3,22 +3,44 @@
     <div style="background-color: #fff; position: sticky;">
       <buttons :current="0"></buttons>
     </div>
-    <div class="d-flex recommend-content">
-      <div class="mt-3">
-        <PositionList v-if="items.length" :items="items"></PositionList>
+    <Empty v-if="!items.length" message="暂无职位推荐,请前往职位查看其他职位~" class="mt-3 py-15"></Empty>
+    <template v-else>
+      <div class="d-flex">
+        <div class="mt-3">
+          <PositionList v-if="items.length" :items="items" @selectChange="selectChange"></PositionList>
+        </div>
+        <div class="position-details ml-1" style="flex: 1; overflow: hidden;">
+          <div class="position-content">
+            <recommendDetails 
+              v-if="showRecommendDetails && jobId"
+              :defaultWidth="false"
+              :showContentRight="false"
+              :propJobId="jobId"
+            ></recommendDetails>
+          </div>
+        </div>
       </div>
-      <div style="flex: 1;" class="ml-3">right-details</div>
-    </div>
+    </template>
   </div>
 </template>
 
 <script setup>
 defineOptions({ name: 'personalPositionRecommend'})
 import buttons from '@/views/recruit/personal/components/buttons.vue'
-import { ref, reactive } from 'vue'
-import { getJobAdvertisedSearch } from '@/api/position'
-import { dealDictObjData } from '@/utils/position'
+import { ref, reactive, nextTick } from 'vue'
+// import { getJobAdvertisedSearch } from '@/api/position'
+import { getPromotedPosition } from '@/api/position'
+import { dealDictArrayData } from '@/utils/position'
 import PositionList from './components/item'
+import recommendDetails from '@/views/recruit/personal/position/components/details.vue'
+
+const jobId = ref('')
+const showRecommendDetails = ref(false)
+const selectChange = (item) => {
+  jobId.value = item.job.id
+  showRecommendDetails.value = false
+  nextTick(() => showRecommendDetails.value = true)
+}
 
 const query = reactive({
   pageNum: 1,
@@ -26,28 +48,71 @@ const query = reactive({
 })
 const items = ref([])
 
-const getData = async () => {
-  const { list } = await getJobAdvertisedSearch(query)
-  if (!list.length) return
-  items.value = list.map(e => {
-    e.job = { ...e.job, ...dealDictObjData({}, e.job) }
-    e.enterprise = { ...e.enterprise, ...dealDictObjData({}, e.enterprise) }
-    return e
+const getList = async () => {
+  const { list } = await getPromotedPosition(query)
+  if (!list.length) return items.value = []
+  items.value = list.map(e=> {
+    const id = e?.id || ''
+    if (!id) return ''
+    jobId.value = id
   })
-  if (items.value.length) {
-    items.value[0].active = true
+  // 处理数据
+  items.value = dealDictArrayData([], list)
+  if (items.value?.length) showRecommendDetails.value = true
+  else {
+    // if (query.pageNum> 3) return
+    // query.pageNum++
+    // getList()
   }
 }
-getData()
+getList()
+
+// const getData = async () => {
+//   const { list } = await getJobAdvertisedSearch(query)
+//   if (!list.length) return
+//   items.value = list.map((e, index) => {
+//     const id = e?.job?.id || ''
+//     if (!id) return ''
+//     jobId.value = id
+//     showRecommendDetails.value = true
+//     e.job = { ...e.job, ...dealDictObjData({}, e.job) }
+//     e.enterprise = { ...e.enterprise, ...dealDictObjData({}, e.enterprise) }
+//     e.active = index ? false : true
+//     return e
+//   }).filter(Boolean)
+//   console.log('personalPositionRecommend-data', items.value)
+// }
+// getData()
+
 </script>
 
 <style scoped lang="scss">
-.recommend-content {
-  height: calc(100vh - 100px);
-  overflow-y: auto;
+.position-details {
+  position: sticky;
+  top: 62px;
+  border-radius: 12px;
+  background-color: #fff;
+  margin-top: 12px;
+  height: calc(100vh - 127px);
+  widows: 100%;
+  overflow: hidden;
+  .position-content {
+    height: 100%;
+    width: 100%;
+    padding-right: 4px;
+    overflow-y: auto;
+  }
 }
 ::-webkit-scrollbar {
-  width: 0;
-  height: 0;
+  width: 4px;
+  height: 10px;
+}
+::-webkit-scrollbar-thumb, .temporaryAdd ::-webkit-scrollbar-thumb, .details_edit ::-webkit-scrollbar-thumb {
+  // 滚动条-颜色
+  background: #c3c3c379;
+}
+::-webkit-scrollbar-track, .temporaryAdd ::-webkit-scrollbar-track, .details_edit ::-webkit-scrollbar-track {
+  // 滚动条-底色
+  background: #e5e5e58f;
 }
 </style>