Bladeren bron

招聘会:新增职位、克隆职位

Xiao_123 1 maand geleden
bovenliggende
commit
545db9bc38

+ 3 - 2
src/api/recruit/enterprise/jobFair.js

@@ -8,9 +8,10 @@ export const getCheckJobFairPermission = async (id) => {
 }
 
 // 获得招聘会列表
-export const getJobFairList = async () => {
+export const getJobFairList = async (params) => {
   return await request.get({
-    url: '/app-api/menduner/system/recruit/job-fair/list'
+    url: '/app-api/menduner/system/recruit/job-fair/list',
+    params
   })
 }
 

+ 1 - 0
src/views/recruit/enterprise/hirePosition/index.vue

@@ -53,6 +53,7 @@ const tipsText = ref(t('common.noData'))
 const query = ref({
   pageSize: 10,
   pageNo: 1,
+  jobFairId: 0, // 过滤非招聘会职位
   status: 0, // 0招聘中 1已关闭
   hasExpiredData: false, // true 到期职位
   hire: true

+ 0 - 216
src/views/recruit/enterprise/jobFair/components/job.vue

@@ -1,216 +0,0 @@
-<template>
-  <div>
-    <div class="text-end mb-3">
-      <v-btn color="primary" class="mr-3" @click="handleAdd">新增职位</v-btn>
-      <v-btn color="primary" variant="outlined" @click="handleJoin">选择已发布的职位加入招聘会</v-btn>
-      <!-- <v-btn :loading="exportLoading" prepend-icon="mdi-export-variant" color="primary" variant="tonal" class="ml-3" @click="handleExport">职位导出</v-btn> -->
-    </div>
-    <JobItem :items="jobList" @refresh="getJobList"></JobItem>
-
-    <v-navigation-drawer v-model="showDrawer" location="right" temporary width="600">
-      <Loading :visible="positionLoading" :contained="true"></Loading>
-      <div class="resume-box">
-        <div class="resume-header">
-          <div class="resume-title mr-5">已发布职位</div>
-        </div>
-      </div>
-      <div class="px-3">
-        <v-text-field
-          v-model="positionSearch"
-          append-inner-icon="mdi-magnify"
-          density="compact"
-          :label="t('position.positionName')"
-          variant="outlined"
-          hide-details
-          color="primary"
-          single-line
-          @click:append-inner="getPositionList"
-          @keyup.enter="getPositionList"
-        ></v-text-field>
-      </div>
-      <div class="pa-3" v-if="positionItems.length">
-        <div v-for="val in positionItems" :key="val.id" class="itemBox mb-3" style="height: 80px;">
-          <div class="d-flex justify-space-between" style="padding: 10px 20px;">
-            <div class="position">
-              <div class="d-flex align-center justify-space-between">
-                <!-- <span v-if="val.name.indexOf('style')" v-html="val.name" class="position-name"></span> -->
-                <span class="position-name">{{ formatName(val.name) }}</span>
-                <div>
-                  <v-btn size="small" color="primary" @click="handleTo(val)">添加至招聘会</v-btn>
-                </div>
-              </div>
-              <div :class="['mt-3', 'other-info', 'ellipsis']">
-                <span>{{ val.areaName ? val.area?.str : '全国' }}</span>
-                <span class="lines" v-if="val.eduName"></span>
-                <span>{{ val.eduName }}</span>
-                <span class="lines"></span>
-                <span>{{ val.expName }}</span>
-                <span class="lines"></span>
-                <span v-if="!val.payFrom && !val.payTo">面议</span>
-                <span v-else>{{ val.payFrom ? val.payFrom + '-' : '' }}{{ val.payTo }}{{ val.payName ? '/' + val.payName : '' }}</span>
-                <span class="lines" v-if="val.positionName"></span>
-                <span>{{ val.positionName }}</span>
-              </div>
-            </div>
-          </div>
-        </div>
-        <CtPagination
-          v-if="total"
-          :total="positionTotal"
-          :page="positionPageInfo.pageNo"
-          :limit="positionPageInfo.pageSize"
-          @handleChange="handleChangePage"
-        ></CtPagination>
-      </div>
-      <Empty v-else :elevation="false"></Empty>
-    </v-navigation-drawer>
-  </div>
-</template>
-
-<script setup>
-defineOptions({ name: 'jobFairJob'})
-import { ref } from 'vue'
-import { getJobFairPosition } from '@/api/recruit/enterprise/jobFair'
-import { dealDictArrayData } from '@/utils/position.js'
-import JobItem from '../job/item.vue'
-import { useRouter, useRoute } from 'vue-router'
-import { useI18n } from '@/hooks/web/useI18n'
-import Snackbar from '@/plugins/snackbar'
-import { getEnterprisePubJobTypePermission } from '@/api/recruit/enterprise/position'
-import { getJobAdvertisedList } from '@/api/position'
-import download from '@/utils/download'
-import { formatName } from '@/utils/getText'
-// import Confirm from '@/plugins/confirm'
-
-const props = defineProps({ id: [String, Number]})
-const router = useRouter()
-const route = useRoute()
-const { t } = useI18n()
-
-// 职位列表
-const jobList = ref([])
-
-const positionItems = ref([])
-const positionTotal = ref(0)
-const positionLoading = ref(false)
-const total = ref(0)
-const positionPageInfo = ref({
-  pageSize: 10,
-  pageNo: 1,
-})
-const positionSearch = ref('')
-
-
-const getJobList = async () => {
-  const data = await getJobFairPosition(props.id)
-  if (!data || !data.length) return jobList.value = []
-  jobList.value = dealDictArrayData([], data)
-}
-
-const handleAdd = async () => {
-  const data = await getEnterprisePubJobTypePermission()
-  if (!data || !data.length) return Snackbar.warning('没有该操作权限,请联系平台管理员升级后再试')
-  router.push(`/recruit/enterprise/jobFair/details/${route.params.id}/edit`)
-}
-
-const showDrawer = ref(false)
-const handleJoin = async () => {
-  getPositionList()
-  showDrawer.value = true
-}
-
-const handleChangePage = (index) => {
-  positionPageInfo.value.pageNo = index
-  getPositionList()
-}
-
-const handleTo = (val) => {
-  router.push(`/recruit/enterprise/jobFair/details/${route.params.id}/edit?id=${val.id}`)
-}
-
-// 获取职位列表
-const getPositionList = async () => {
-  positionLoading.value = true
-  const query = {
-    ...positionPageInfo.value,
-    status: 0,
-    hasExpiredData: false,
-    hire: false
-  }
-  if ( positionSearch.value) {
-    Object.assign(query, {
-      name: positionSearch.value,
-    })
-  }
-  try {
-    const { list, total } = await getJobAdvertisedList(query)
-    positionTotal.value = total
-    positionItems.value = list.length ? dealDictArrayData([], list) : []
-  } finally {
-    positionLoading.value = false
-  }
-}
-getJobList()
-
-// // 导出参加双选会职位
-// const exportLoading = ref(false)
-// const handleExport = async () => {
-//   exportLoading.value = true
-//   try {
-//     const data = await getJobFairAdvertisedExport()
-//     download.excel(data, '招聘会职位列表' + '.xlsx')
-//   } finally {
-//     exportLoading.value = false
-//   }
-// }
-
-</script>
-
-<style scoped lang="scss">
-.resume-box {
-  padding-top: 120px;
-}
-
-.itemBox {
-  position: relative;
-  border: 1px solid #e5e6eb;
-}
-.position-name {
-  color: var(--color-333);
-  font-size: 19px;
-}
-.position {
-  width: 100%;
-  position: relative;
-  .item-select {
-    position: absolute;
-    left: -8px;
-    top: -13px;
-  }
-}
-.lines {
-  display: inline-block;
-  width: 1px;
-  height: 17px;
-  vertical-align: middle;
-  background-color: #e0e0e0;
-  margin: 0 10px;
-}
-.other-info {
-  font-size: 15px;
-  color: var(--color-666);
-}
-.bottom {
-  position: absolute;
-  bottom: 0;
-  left: 0;
-  width: 100%;
-  height: 40px;
-  background-color: #f7f8fa;
-  font-size: 14px;
-  color: var(--color-888);
-}
-.actions:hover {
-  color: var(--v-primary-base);
-}
-</style>

+ 5 - 3
src/views/recruit/enterprise/jobFair/job/item.vue → src/views/recruit/enterprise/jobFair/components/jobItem.vue

@@ -36,8 +36,9 @@
             </v-avatar>
             <div class="ml-3" v-ellipse-tooltip>{{ val.hrName }}</div>
           </div>
-          <div class="bottom-item" v-ellipse-tooltip :style="{'text-align': val.hrName ? 'center' : 'start'}">到期时间:{{ val.expireTime ? timesTampChange(val.expireTime, 'Y-M-D') : '长期有效' }}</div>
-          <div class="d-flex justify-end bottom-item">
+          <div v-else></div>
+          <!-- 进行中的招聘会方可操作 -->
+          <div class="d-flex justify-end bottom-item" v-if="status">
             <span class="cursor-pointer actions" @click="handleEdit(val)">编辑</span>
             <span class="lines"></span>
             <span class="cursor-pointer actions" @click="handleRemove(val)">移出招聘会</span>
@@ -65,7 +66,8 @@ import { getUserAvatar } from '@/utils/avatar'
 
 const emit = defineEmits(['refresh'])
 defineProps({
-  items: Array
+  items: Array,
+  status: Boolean // 招聘会状态
 })
 
 const loading = ref(false)

+ 0 - 471
src/views/recruit/enterprise/jobFair/components/resume.vue

@@ -1,471 +0,0 @@
-<template>
-  <div class="d-flex flex-column">
-    <v-tabs v-model="tab" align-tabs="start" color="primary" bg-color="#f7f8fa" @update:modelValue="handleChange">
-      <v-tab v-for="k in tabList" :value="k.value" :key="k.value">{{ k.label }}</v-tab>
-    </v-tabs>
-    <!-- <Screen :tab="tab" @search="handleScreen" @reset="handleScreenReset" @select="handleSelect" @change="handleChangeBounty"></Screen> -->
-    <v-tabs-window v-model="tab" class="mt-1">
-      <v-tabs-window-item v-for="k in tabList" :value="k.value" :key="k.value">
-        <CtTable
-          class="mt-3"
-          :items="k.items"
-          :headers="headers"
-          :loading="false"
-          :elevation="0"
-          height="60vh"
-          :disableSort="true"
-          :isTools="false"
-          :showPage="true"
-          :total="k.total"
-          :page-info="k.pageInfo"
-          itemKey="id"
-          @pageHandleChange="handleChangePage"
-        >
-          <template #studentId="{ item }">
-            <div class="d-flex align-center">
-              <v-badge
-                v-if="item.student?.studentSex"
-                bordered
-                offset-y="6"
-                :color="item.student?.studentSex === '男' ? '#1867c0' : 'error'"
-                :icon="item.student?.studentSex === '男' ? 'mdi-gender-male' : 'mdi-gender-female'"
-              >
-                <v-avatar size="40" :image="getUserAvatar(item.student.studentHeadImg, item.student.studentSex)"></v-avatar>
-              </v-badge>
-              <v-avatar v-else size="40" :image="getUserAvatar(item.student.studentHeadImg, item.student.sex)"></v-avatar>
-              <span class="ml-3">{{ item?.student?.studentName }}</span>
-            </div>
-          </template>
-          <template #major="{ item }">
-            {{ item.student?.majorName }}
-            {{ item.student?.majorName && item.student?.schoolDepartmentName ? ' - ' : '' }}
-            {{ item.student?.schoolDepartmentName }}
-          </template>
-          <template #status="{ item}">
-            {{ statusType[item.status] }}
-          </template>
-          <template #actions="{ item }">
-            <v-btn color="primary" variant="text" @click="handlePreviewResume(item)">查看附件</v-btn>
-            <template v-if="tab === 0">
-              <v-btn color="primary" variant="text" @click="handleInterviewInvite(item)">邀请面试</v-btn>
-            </template>
-            <template v-if="tab === 1">
-              <v-btn v-if="[21,22].includes(item.status)" color="primary " variant="text" @click="handleEliminate(item)">不合适</v-btn>
-              <v-btn v-if="item.status === 22" color="primary" variant="text" @click="handleEnterByEnterprise(item)">入职</v-btn>
-            </template>
-            <v-btn v-if="item.status === 2" color="primary" variant="text" @click="handleEditJoinDate(item)">修改到岗时间</v-btn>
-          </template>
-        </CtTable>
-      </v-tabs-window-item>
-    </v-tabs-window>
-    
-
-    <!-- 邀请面试 -->
-    <CtDialog
-      :visible="showInvite"
-      :widthType="4"
-      titleClass="text-h6"
-      title="面试信息"
-      @close="showInvite = false"
-      @submit="handleEditSubmit"
-    >
-      <CtForm ref="CtFormInviteRef" :items="formItems" style="height: 500px;"></CtForm>
-    </CtDialog>
-
-    <!-- 入职 -->
-    <CtDialog
-      :visible="showAccess"
-      :widthType="4"
-      titleClass="text-h6"
-      title="入职信息"
-      @close="showAccess = false"
-      @submit="handleAccess"
-    >
-      <CtForm ref="CtFormAccessRef" :items="AccessItems" style="height: 500px;"></CtForm>
-    </CtDialog>
-
-    <!-- 修改到岗时间 -->
-    <CtDialog
-      :visible="showEditJoinDate"
-      :widthType="4"
-      titleClass="text-h6"
-      title="修改到岗时间"
-      @close="showEditJoinDate = false"
-      @submit="handleSubmitEditJoinDate"
-    >
-      <DatePicker v-model="editJoinDateItems.value" :item="editJoinDateItems"></DatePicker>
-    </CtDialog>
-
-    <!-- 不合适 -->
-    <CtDialog
-      :visible="showReject"
-      :widthType="4"
-      titleClass="text-h6"
-      title="拒绝理由"
-      @close="showReject = false"
-      @submit="handleReject"
-    >
-      <CtForm ref="CtFormRejectRef" :items="rejectItems"></CtForm>
-    </CtDialog>
-  </div>
-</template>
-
-<script setup>
-defineOptions({ name: 'jobFairResume'})
-import { computed, ref } from 'vue'
-import { useRoute } from 'vue-router'
-import { useI18n } from '@/hooks/web/useI18n'
-import Snackbar from '@/plugins/snackbar'
-import { getResumeList, resumeCancel, resumeAccept, resumeInterview, updateJoinDate } from '@/api/recruit/enterprise/jobFair'
-import { useUserStore } from '@/store/user'
-import { getDict } from '@/hooks/web/useDictionaries'
-import { previewFile } from '@/utils'
-import { getUserAvatar } from '@/utils/avatar'
-import DatePicker from '@/components/FormUI/datePicker'
-import { timesTampChange } from '@/utils/date'
-import { formatName } from '@/utils/getText'
-import { getInterviewInviteDefaultTime } from '@/utils/date'
-
-const userStore = useUserStore() 
-const route = useRoute()
-const { t } = useI18n()
-
-const statusType = {
-  0:'学生投递',
-  1:'企业邀请',
-  2:'同意入职',
-  3:'老师邀请',
-  4:'待学生确认入职',
-  14:'取消申请',
-  15:'不合适',
-  16:'学生拒绝老师邀请',
-  17:'学生实习结束',
-  20:'撤回投递',
-  21:'已邀约',
-  22:'学生同意面试邀请',
-  23:'学生拒绝面试邀请'
-}
-const tab = ref(0)
-const tabList = ref([
-  {
-    label: '投递简历',
-    value: 0,
-    statuss: [0],
-    items: [],
-    total: 0,
-    pageInfo: {
-      current: 1,
-      size: 10
-    }
-  },
-  {
-    label: '面试邀约',
-    value: 1,
-    statuss: [4, 21, 22, 23],
-    items: [],
-    total: 0,
-    pageInfo: {
-      current: 1,
-      size: 10
-    }
-  },
-  {
-    label: '已入职',
-    value: 2,
-    statuss: [2],
-    items: [],
-    total: 0,
-    pageInfo: {
-      current: 1,
-      size: 10
-    }
-  },
-  {
-    label: '不合适',
-    value: 3,
-    statuss: [15],
-    items: [],
-    total: 0,
-    pageInfo: {
-      current: 1,
-      size: 10
-    }
-  }
-])
-
-const headers = computed(() => {
-  const item = [
-    { title: '姓名', value: 'studentId', sortable: false },
-    { title: '学校', value: 'student.schoolName', sortable: false },
-    { title: '专业', value: 'major', sortable: false },
-    { title: '应聘职位', key: 'enterpriseRecruit.enterpriseRecruitJobName', sortable: false, value: item => formatName(item?.enterpriseRecruit?.enterpriseRecruitJobName) },
-    { title: '岗位薪资',
-      key: 'pay',
-      value: ({ enterpriseRecruit }) => {
-        if (!enterpriseRecruit) {
-          return '面议'
-        }
-        const { payFrom, payTo, payUnit } = enterpriseRecruit
-        return `${payFrom}${payFrom && payTo ? '-' : ''}${payTo}${payUnit ? '/' : ''}${payUnit}`
-      },
-      sortable: false
-    },
-    { title: '联系电话', value: 'student.phone', sortable: false },
-    { title: '到岗时间', key: 'jobJoinDate', sortable: false, value: item => timesTampChange(item.jobJoinDate, 'Y-M-D') },
-    { title: '状态', value: 'status', sortable: false },
-    { title: '操作', value: 'actions', sortable: false }
-  ]
-  return item
-})
-
-const CtFormInviteRef = ref()
-const CtFormRejectRef = ref()
-const CtFormAccessRef = ref()
-
-const formItems = ref({
-  options: [
-    {
-      type: 'datePicker',
-      mode: 'datetime',
-      labelWidth: 78,
-      value: '',
-      key: 'interviewDate',
-      label: '面试时间 *',
-      value: getInterviewInviteDefaultTime().timeStamp,
-      format: "YYYY/MM/DD HH:mm",
-      defaultTime: getInterviewInviteDefaultTime().time,
-      flexStyle: 'mb-7',
-      disabledDate: true,
-      rules: [v => !!v || '请选择面试时间']
-    },
-    {
-      type: 'text',
-      key: 'name',
-      value: '',
-      noParam: true,
-      disabled: true,
-      label: '面试者'
-    },
-    {
-      type: 'text',
-      key: 'position',
-      value: '',
-      noParam: true,
-      disabled: true,
-      label: '面试岗位'
-    }
-  ]
-})
-
-const rejectItems = ref({
-  options: [
-    {
-      type: 'textarea',
-      key: 'refuseMsg',
-      label: '拒绝理由 *',
-      rules: [v => !!v || '请填写拒绝理由']
-    }
-  ]
-})
-
-const AccessItems = ref({
-  options: [
-    {
-      type: 'datePicker',
-      mode: 'datetime',
-      labelWidth: 78,
-      value: '',
-      key: 'jobJoinDate',
-      label: '到岗时间 *',
-      format: "YYYY/MM/DD HH:mm",
-      flexStyle: 'mb-7',
-      disabledDate: true,
-      rules: [v => !!v || '请选择到岗时间']
-    },
-    {
-      type: 'text',
-      key: 'name',
-      value: '',
-      noParam: true,
-      disabled: true,
-      label: '入职人员'
-    },
-    {
-      type: 'text',
-      key: 'position',
-      value: '',
-      noParam: true,
-      disabled: true,
-      label: '入职岗位'
-    }
-  ]
-})
-const editJoinDateItems = ref({
-  value: '',
-  id: '',
-  mode: 'date',
-  placeholder: '请选择要修改的到岗时间',
-  teleported: true,
-  disabledDate: true
-})
-
-const loading = ref(false)
-const itemData = ref({})
-const showInvite = ref(false)
-const showReject = ref(false)
-const showAccess = ref(false)
-const showEditJoinDate = ref(false)
-
-// 状态字典
-const statusList = ref([])
-
-getDict('menduner_interview_invite_status').then(({data}) => {
-  if (data && data.length) statusList.value = data
-})
-
-const handleChangePage = (index) => {
-  tabList.value[tab.value].pageInfo.current = index
-  getList()
-}
-
-const handleChange = () => {
-  tabList.value[tab.value].pageInfo.current = 1
-  getList()
-}
-
-// 查看简历
-const handlePreviewResume = async (item) => {
-  const url = item.studentInfoVo?.studentBiographicalNotes?.fileUrl
-  if (!url) return
-  previewFile(url)
-}
-// 邀请面试
-const handleInterviewInvite = (item) => {
-  formItems.value.options.forEach(e => {
-    if (e.key === 'name') {
-      e.value = item.student?.studentName
-    }
-    if (e.key === 'position') {
-      e.value = item.enterpriseRecruit.enterpriseRecruitJobName
-    }
-  })
-  itemData.value = item
-  showInvite.value = true
-}
-// 提交邀请
-const handleEditSubmit = async () => {
-  const { valid } = await CtFormInviteRef.value.formRef.validate()
-  if (!valid) return
-  const query = formItems.value.options.reduce((acc, cur) => {
-    if (cur.noParam) {
-      return acc
-    }
-    acc[cur.key] = cur.value
-    return acc
-  }, {
-    practiceSubmitRecordId: itemData.value.practiceSubmitRecordId,
-    studentId: itemData.value.studentId
-  })
-  if (!query?.interviewDate) return Snackbar.warning('请选择邀请时间')
-
-  await resumeInterview(query)
-  
-  Snackbar.success(t('common.operationSuccessful'))
-  showInvite.value = false
-  getList()
-}
-
-const handleEliminate = (item) => {
-  itemData.value = item
-  showReject.value = true
-}
-// 不合适
-const handleReject = async () => {
-  const { valid } = await CtFormRejectRef.value.formRef.validate()
-  if (!valid) return
-  const query = rejectItems.value.options.reduce((acc, cur) => {
-    acc[cur.key] = cur.value
-    return acc
-  }, {
-    practiceSubmitRecordId: itemData.value.practiceSubmitRecordId,
-    enterpriseId: itemData.value.enterpriseId
-  })
-  await resumeCancel(query)
-  
-  Snackbar.success(t('common.operationSuccessful'))
-  showReject.value = false
-  getList()
-}
-
-// 入职
-const handleEnterByEnterprise = async (item) => {
-  AccessItems.value.options.forEach(e => {
-    if (e.key === 'name') {
-      e.value = item.student?.studentName
-    }
-    if (e.key === 'position') {
-      e.value = item.enterpriseRecruit.enterpriseRecruitJobName
-    }
-  })
-  itemData.value = item
-  showAccess.value = true
-}
-const handleAccess = async () => {
-  const { valid } = await CtFormAccessRef.value.formRef.validate()
-  if (!valid) return
-  const query = AccessItems.value.options.reduce((acc, cur) => {
-    if (cur.noParam) {
-      return acc
-    }
-    acc[cur.key] = cur.value
-    return acc
-  }, {
-    practiceSubmitRecordId: itemData.value.practiceSubmitRecordId,
-    studentId: itemData.value.studentId
-  })
-  await resumeAccept(query)
-  Snackbar.success(t('common.operationSuccessful'))
-  showAccess.value = false
-  getList()
-}
-
-// 修改到岗时间
-const handleEditJoinDate = (item) => {
-  editJoinDateItems.value.id = item.practiceSubmitRecordId
-  editJoinDateItems.value.value = item.jobJoinDate
-  showEditJoinDate.value = true
-}
-
-const handleSubmitEditJoinDate = async () => {
-  if (!editJoinDateItems.value.value) return Snackbar.warning('请选择要修改的日期')
-  await updateJoinDate({ jobJoinDate: editJoinDateItems.value.value, practiceSubmitRecordId: editJoinDateItems.value.id })
-
-  Snackbar.success('修改成功')
-  editJoinDateItems.value.value = ''
-  editJoinDateItems.value.id = ''
-  showEditJoinDate.value = false
-  getList()
-}
-
-const getList = async () => {
-  loading.value = true
-  try {
-    const res = await getResumeList({
-      ...tabList.value[tab.value].pageInfo,
-      jobFairId: route.params.id,
-      enterpriseId: userStore.entBaseInfo.enterpriseId,
-      statuss: tabList.value[tab.value].statuss
-    })
-    tabList.value[tab.value].items = res.records
-    tabList.value[tab.value].total = res.total
-  } finally {
-    loading.value = false
-  }
-}
-
-getList()
-</script>
-
-<style scoped lang="scss">
-
-</style>

+ 53 - 52
src/views/recruit/enterprise/jobFair/details.vue

@@ -2,20 +2,42 @@
   <v-card class="card-box pa-4" :style="`background-color: ${jobFairInfo?.backgroundColour}`">
     <Loading :visible="jobListLoading" :contained="true"></Loading>
     <div class="position-relative">
-      <div class="text-center my-6 mx-10 px-15 font-weight-bold font-size-20" style="color: #fff">{{ jobFairInfo?.title?.replace(/<\/?p[^>]*>/gi, '') }}</div>
-      <div class="d-flex justify-space-between mb-3 align-center">
-        <div>
-          <span v-if="jobNum && Number(jobNum)" style="color: #fff;">剩余可发布职位数:{{ jobNum }}个</span>
-        </div>
-        <div>
-          <!-- <v-btn color="#fff" :style="`color: ${jobFairInfo?.backgroundColour || 'var(--v-primary-base)'}`" @click="handleAdd">新增职位</v-btn> -->
-          <v-btn color="#fff" class="ml-3" :style="`color: ${jobFairInfo?.backgroundColour || 'var(--v-primary-base)'}`" prepend-icon="mdi-refresh" @click="getJobList('刷新')">刷新</v-btn>
-          <v-btn color="#fff" class="mx-3" :style="`color: ${jobFairInfo?.backgroundColour || 'var(--v-primary-base)'}`" @click="handleJoin">选择已发布的职位加入招聘会</v-btn>
-          <v-btn color="#fff" v-if="jobFairInfo?.contentImg" :style="`color: ${jobFairInfo?.backgroundColour || 'var(--v-primary-base)'}`" prepend-icon="mdi-share" @click="handleShare">我的分享海报</v-btn>
-        </div>
+      <div class="text-center my-6 mx-10 px-15 font-weight-bold font-size-20" style="color: #fff">
+        {{ jobFairInfo?.title?.replace(/<\/?p[^>]*>/gi, '') }}
+        <span v-if="jobFairInfo?.status === '1'">(已结束)</span>
+      </div>
+      <!-- 操作按钮组: 进行中的招聘会展示 -->
+      <div class="d-flex justify-end mb-3 align-center" v-if="jobFairInfo?.status === '0'">
+        <v-btn
+          color="#fff"
+          :style="`color: ${jobFairInfo?.backgroundColour || 'var(--v-primary-base)'}`"
+          @click="handleAdd"
+          prepend-icon="mdi-plus"
+        >新增职位</v-btn>
+        <v-btn
+          color="#fff"
+          class="ml-3"
+          :style="`color: ${jobFairInfo?.backgroundColour || 'var(--v-primary-base)'}`"
+          prepend-icon="mdi-refresh"
+          @click="getJobList('刷新')"
+        >刷新</v-btn>
+        <v-btn
+          color="#fff"
+          class="mx-3"
+          :style="`color: ${jobFairInfo?.backgroundColour || 'var(--v-primary-base)'}`"
+          @click="handleJoin"
+          prepend-icon="mdi-check-circle-outline"
+        >克隆普通职位加入招聘会</v-btn>
+        <v-btn
+          color="#fff"
+          v-if="jobFairInfo?.contentImg"
+          :style="`color: ${jobFairInfo?.backgroundColour || 'var(--v-primary-base)'}`"
+          prepend-icon="mdi-share"
+          @click="handleShare"
+        >我的分享海报</v-btn>
       </div>
       <div v-if="jobListLoading" class="centerText color-777">加载中...</div>
-      <JobItem v-else :items="jobList" @refresh="getJobList(), getJobNum()"></JobItem>
+      <JobItem v-else :items="jobList" :status="jobFairInfo?.status === '0'" @refresh="getJobList"></JobItem>
 
       <v-navigation-drawer v-model="showDrawer" location="right" temporary width="600">
         <Loading :visible="positionLoading" :contained="true"></Loading>
@@ -71,12 +93,12 @@
                   </v-avatar>
                   <div class="ml-3" v-ellipse-tooltip style="max-width: calc(100% - 34px);">{{ val.hrName }}</div>
                 </div>
-                <v-btn size="small" color="primary" @click="handleTo(val)">添加至招聘会</v-btn>
+                <v-btn size="small" color="primary" @click="handleTo(val)">克隆加入</v-btn>
               </div>
             </div>
           </div>
           <CtPagination
-            v-if="total"
+            v-if="positionTotal"
             :total="positionTotal"
             :page="positionPageInfo.pageNo"
             :limit="positionPageInfo.pageSize"
@@ -99,33 +121,29 @@
           @success="handlePreview"
         ></JobFairEntShare>
       </div>
-      <!-- 招聘会分享按钮 -->
-      <!-- <div v-if="jobFairInfo?.contentImg" style="position: absolute; right: 12px; top: -16px; z-index: 999;">
-        <div class="cursor-pointer pa-2 white-bgc" style="border-radius: 8px;" @click.stop="handleShare">
-          <v-icon color="primary" size="26">mdi-share</v-icon>
-        </div>
-      </div> -->
     </div>
   </v-card>
 
   <Loading :visible="entryLoading" />
 
+  <!-- 分享海报预览 -->
   <PreviewImage v-if="showPreview" :urlList="[previewSrc]" :fileName="enterpriseName" @close="showPreview = !showPreview, showShare = false" />
 </template>
 
 <script setup>
 defineOptions({ name: 'jobFairJob'})
 import { ref } from 'vue'
-import { getJobFairPosition, getJobFair, getJobFairRights, joinJobFairPosition, getJobFairPositionList } from '@/api/recruit/enterprise/jobFair'
+import { getJobFairPosition, getJobFair } from '@/api/recruit/enterprise/jobFair'
 import { dealDictArrayData } from '@/utils/position.js'
-import JobItem from './job/item.vue'
+import JobItem from './components/jobItem.vue'
 import { useRouter, useRoute } from 'vue-router'
 import { useI18n } from '@/hooks/web/useI18n'
 import Snackbar from '@/plugins/snackbar'
-// import { getEnterprisePubJobTypePermission } from '@/api/recruit/enterprise/position'
+import { getEnterprisePubJobTypePermission } from '@/api/recruit/enterprise/position'
 import { formatName } from '@/utils/getText'
 import { getUserAvatar } from '@/utils/avatar'
 import JobFairEntShare from '@/views/recruit/components/jobFairEntShare'
+import { getJobAdvertisedList } from '@/api/position'
 
 const router = useRouter()
 const route = useRoute()
@@ -138,7 +156,6 @@ const jobList = ref([])
 const positionItems = ref([])
 const positionTotal = ref(0)
 const positionLoading = ref(false)
-const total = ref(0)
 const positionPageInfo = ref({
   pageSize: 10,
   pageNo: 1,
@@ -167,11 +184,12 @@ const getJobList = async (type) => {
   }
 }
 
-// const handleAdd = async () => {
-//   const data = await getEnterprisePubJobTypePermission()
-//   if (!data || !data.length) return Snackbar.warning('没有该操作权限,请联系平台管理员升级后再试')
-//   router.push(`/recruit/enterprise/jobFair/details/${id}/edit`)
-// }
+// 新增招聘会职位
+const handleAdd = async () => {
+  const data = await getEnterprisePubJobTypePermission()
+  if (!data || !data.length) return Snackbar.warning('没有该操作权限,请联系平台管理员升级后再试')
+  router.push(`/recruit/enterprise/jobFair/details/${id}/edit`)
+}
 
 const showDrawer = ref(false)
 const handleJoin = async () => {
@@ -184,35 +202,26 @@ const handleChangePage = (index) => {
   getPositionList()
 }
 
-// 添加至招聘会
+// 克隆加入招聘会
 const entryLoading = ref(false)
 const handleTo = async (val) => {
-  entryLoading.value = true
-  try {
-    await joinJobFairPosition({ jobFairId: id, jobId: val.id })
-    Snackbar.success('加入成功')
-    showDrawer.value = false
-    getJobNum()
-    getJobList()
-  } finally {
-    entryLoading.value = false
-  }
+  router.push(`/recruit/enterprise/jobFair/details/${id}/edit?id=${val.id}&clone=1`)
 }
 
-// 获取职位列表
+// 获取所有职位列表(普通职位、往届招聘会职位)
 const getPositionList = async () => {
   positionLoading.value = true
   const query = {
     ...positionPageInfo.value,
-    jobFairId: id
+    // jobFairId: id
   }
   if ( positionSearch.value) {
     Object.assign(query, {
-      name: positionSearch.value,
+      name: positionSearch.value
     })
   }
   try {
-    const { list, total } = await getJobFairPositionList(query)
+    const { list, total } = await getJobAdvertisedList(query)
     positionTotal.value = total
     positionItems.value = list.length ? dealDictArrayData([], list) : []
   } finally {
@@ -226,14 +235,6 @@ const handleSearch = () => {
 
 getJobList()
 
-// 可发布职位数
-const jobNum = ref(0)
-const getJobNum = async () => {
-  const result = await getJobFairRights(id)
-  jobNum.value = result?.num || 0
-}
-getJobNum()
-
 // 获取招聘会信息
 const jobFairInfo = ref({})
 const getJobFairInfo = async () => {

+ 27 - 3
src/views/recruit/enterprise/jobFair/index.vue

@@ -1,9 +1,15 @@
 <template>
   <v-card style="min-height: 70vh;">
-    <div v-if="list.length" class="card-box pa-5 ">
+    <div v-if="list.length > 0" class="card-box pa-5">
       <v-card v-for="(k, i) in list" :key="i" class="elevation-3">
         <img :src="k.pcPreviewImg" style="width: 100%; object-fit: contain;" />
         <div class="px-5 py-3">
+          <div class="color-666">
+            活动状态:
+            <span :style="{'color': k.status === '0' ? 'var(--v-primary-base)' : 'var(--v-error-base)'}">
+              {{ k.status === '0' ? '进行中' : '已结束' }}
+            </span>
+          </div>
           <div class="color-666">活动时间:{{ timesTampChange(k.startTime, 'Y-M-D') }}至{{ timesTampChange(k.endTime, 'Y-M-D') }}</div>
           <div class="text-end">
             <v-btn color="primary" variant="outlined" @click.stop="handleBlockEnterprise(k)">立即加入</v-btn>
@@ -12,6 +18,13 @@
       </v-card>
     </div>
     <Empty v-else :elevation="false" message="暂无进行中的招聘会,去看看其他吧~" />
+    <CtPagination
+      v-if="total > 0"
+      :total="total"
+      :page="query.pageNo"
+      :limit="query.pageSize"
+      @handleChange="handleChangePage"
+    ></CtPagination>
   </v-card>
 
   <CtDialog
@@ -75,13 +88,24 @@ import { createTradeOrder } from '@/api/position'
 
 const router = useRouter()
 const list = ref([])
+const total = ref(0)
+const query = ref({
+  pageNo: 1,
+  pageSize: 4
+})
 
 const getList = async () => {
-  const data = await getJobFairList()
-  list.value = data
+  const data = await getJobFairList(query.value)
+  list.value = data.list || []
+  total.value = data.total
 }
 getList()
 
+const handleChangePage = (val) => {
+  query.value.pageNo = val
+  getList()
+}
+
 const timer = ref(null)
 const showDialog = ref(false)
 

+ 13 - 12
src/views/recruit/enterprise/positionManagement/components/add.vue

@@ -10,7 +10,7 @@
         >
           <div>
             <h2 class="mt-n1 headline font-weight-regular">{{ val.title }}</h2>
-            <div class="mb-4 desc">{{ val.desc }}</div>
+            <div class="mb-4 color-666 font-size-13">{{ val.desc }}</div>
             <component :is="val.path" :ref="val.ref" :isFair="props.isFair" :itemData="itemData" @changeType="handleChangeType"></component>
           </div>
         </v-timeline-item>
@@ -222,10 +222,14 @@ const handleSave = async () => {
   submitParams = Object.assign(baseInfo, requirement, { currency_type: 0 }) // currency_type: 写死0(人民币)  source: 0职位管理|1招聘会
 
   submitParams.source = props.isFair ? '2' : submitParams.source ? submitParams.source : '0'  // 职位来源(0职位管理|1众聘职位|2招聘会)
-  submitParams.bizId = props.isFair ? route.params.id : submitParams.bizId ? submitParams.bizId : null 
+  // submitParams.bizId = props.isFair ? route.params.id : submitParams.bizId ? submitParams.bizId : null
+  if (props.isFair) submitParams.bizId = route.params.id // 招聘会职位新增添加招聘会id
 
   if (route.query && route.query.id) submitParams.id = route.query.id // 有id则为编辑
-  console.log('发布职位参数', submitParams)
+  console.log('发布职位参数', submitParams, isClone.value)
+
+  if (isClone.value) delete submitParams.id // 克隆职位删除id
+
   saveEmit()
 }
 
@@ -281,6 +285,7 @@ const handlePayClose = () => {
 }
 
 const show = ref(false)
+const isClone = ref(false)
 // 获取编辑的职位详情
 const getPositionDetail = async (id) => {
   const data = await getJobDetails({ id })
@@ -293,8 +298,11 @@ const getPositionDetail = async (id) => {
 }
 
 // 有id为编辑
-if (route.query?.id) getPositionDetail(route.query.id)
-else show.value = true
+if (route.query?.id) {
+  // 是否为克隆职位
+  isClone.value = route.query.clone === '1'
+  getPositionDetail(route.query.id)
+} else show.value = true
 
 // 取消
 const handleCancel = () => {
@@ -311,13 +319,6 @@ const handleCancel = () => {
 </script>
 
 <style scoped lang="scss">
-.desc {
-  font-size: 13px;
-  color: var(--color-666);
-}
-</style>
-
-<style lang="scss" scoped>
 .mb {
   margin-bottom: 100px;
 }

+ 61 - 61
src/views/recruit/enterprise/positionManagement/components/baseInfo.vue

@@ -1,7 +1,7 @@
 <template>
   <div>
     <CtForm ref="formPageRef" :items="items" style="width: 650px;">
-      <template #bizId="{ item }">
+      <!-- <template #bizId="{ item }">
         <div style="position: relative;">
           <v-checkbox-btn
             v-model="jobFairCheckbox"
@@ -17,10 +17,9 @@
           <div class="bizTips">
             <div>* 该职位如需加入到招聘会中,请勾选上方选项并选择要加入的【招聘会】</div>
             <div>* 未选择【招聘会】将会作为普通职位发布收费!</div>
-            <!-- <div>* 未选择【招聘会】将会作为普通职位发布收费或扣除对应职位发布数!</div> -->
           </div>
         </div>
-      </template>
+      </template> -->
       <template #positionId="{ item }">
         <v-menu :close-delay="1" :open-delay="0" v-bind="$attrs">
           <template v-slot:activator="{  props }">
@@ -64,7 +63,8 @@ import { getRecruitPositionDetails } from '@/api/recruit/enterprise/position'
 import Confirm from '@/plugins/confirm'
 import Snackbar from '@/plugins/snackbar'
 import { useI18n } from '@/hooks/web/useI18n';
-import { getJobFairWhiteList } from '@/api/recruit/enterprise/jobFair'
+// import { getJobFairWhiteList } from '@/api/recruit/enterprise/jobFair'
+import { getJobAdvertised } from '@/api/enterprise'
 
 const { t } = useI18n()
 const props = defineProps({
@@ -85,18 +85,18 @@ let query = reactive({})
 
 const items = ref({
   options: [
-    {
-      slotName: 'bizId',
-      appendClass: 'pb-8',
-      type: 'autocomplete',
-      key: 'bizId',
-      value: null,
-      label: '招聘会',
-      itemText: 'title',
-      itemValue: 'id',
-      disabled: true,
-      items: [],
-    },
+    // {
+    //   slotName: 'bizId',
+    //   appendClass: 'pb-8',
+    //   type: 'autocomplete',
+    //   key: 'bizId',
+    //   value: null,
+    //   label: '招聘会',
+    //   itemText: 'title',
+    //   itemValue: 'id',
+    //   disabled: true,
+    //   items: [],
+    // },
     {
       slotName: 'positionId',
       key: 'positionId',
@@ -146,45 +146,45 @@ const items = ref({
 })
 
 // 获取企业已加入的招聘会列表
-const jobFairLoaded = ref(false)
-const jobFairWhiteList = ref([])
-const getJobFairData = async () => {
-  const data = await getJobFairWhiteList()
-  jobFairWhiteList.value = data?.length ? data : []
-  jobFairLoaded.value = true
-  // 没有加入任何招聘会则不展示招聘会
-  if (!jobFairWhiteList.value?.length) items.value.options = items.value.options.filter(e => e.key !== 'bizId')
-}
-getJobFairData()
+// const jobFairLoaded = ref(false)
+// const jobFairWhiteList = ref([])
+// const getJobFairData = async () => {
+//   const data = await getJobFairWhiteList()
+//   jobFairWhiteList.value = data?.length ? data : []
+//   jobFairLoaded.value = true
+//   // 没有加入任何招聘会则不展示招聘会
+//   if (!jobFairWhiteList.value?.length) items.value.options = items.value.options.filter(e => e.key !== 'bizId')
+// }
+// getJobFairData()
 
-const jobFairValid = async () => {
-  if (props.isFair) {
-    // 招聘会内编辑职位,不需要展示选择招聘会
-    items.value.options = items.value.options.filter(e => e.key !== 'bizId')
-    return
-  }
-  // 加载招聘会白名单列表
-  if (!jobFairLoaded.value) await getJobFairData()
-  const bizIdObj = items.value.options.find(e => e.key === 'bizId') // 招聘会下拉框
-  if (!bizIdObj) return
-  bizIdObj.items = jobFairWhiteList.value // 下拉框内容赋值
-  if (bizIdObj.value) {
-    const index = jobFairWhiteList.value.findIndex(e => e.id === props.itemData.bizId)
-    if (index === -1) bizIdObj.value = null // 招聘会已经关闭 或者已被移除招聘会白名单
-    jobFairCheckboxChange(Boolean(bizIdObj.value), bizIdObj) // 招聘会回显
-  }
-}
-jobFairValid()
+// const jobFairValid = async () => {
+//   if (props.isFair) {
+//     // 招聘会内编辑职位,不需要展示选择招聘会
+//     items.value.options = items.value.options.filter(e => e.key !== 'bizId')
+//     return
+//   }
+//   // 加载招聘会白名单列表
+//   if (!jobFairLoaded.value) await getJobFairData()
+//   const bizIdObj = items.value.options.find(e => e.key === 'bizId') // 招聘会下拉框
+//   if (!bizIdObj) return
+//   bizIdObj.items = jobFairWhiteList.value // 下拉框内容赋值
+//   if (bizIdObj.value) {
+//     const index = jobFairWhiteList.value.findIndex(e => e.id === props.itemData.bizId)
+//     if (index === -1) bizIdObj.value = null // 招聘会已经关闭 或者已被移除招聘会白名单
+//     jobFairCheckboxChange(Boolean(bizIdObj.value), bizIdObj) // 招聘会回显
+//   }
+// }
+// jobFairValid()
 
 // 设置为招聘会职位
 // 现有逻辑:设置为招聘会职位后,职位管理编辑再修改为去掉选中招聘会不会改变数据的jobFairIds字段(用于职位列表判断是否展示招聘会标识),只有去招聘会移除才会改变jobFairIds字段。
-const jobFairCheckbox = ref(false)
-const jobFairCheckboxChange = (bool, item) => {
-  item.value = bool ? (item?.value || null) : null
-  jobFairCheckbox.value = bool
-  item.disabled = bool ? false : true
-  item.label = bool ? '招聘会 *' : '招聘会'
-}
+// const jobFairCheckbox = ref(false)
+// const jobFairCheckboxChange = (bool, item) => {
+//   item.value = bool ? (item?.value || null) : null
+//   jobFairCheckbox.value = bool
+//   item.disabled = bool ? false : true
+//   item.label = bool ? '招聘会 *' : '招聘会'
+// }
 
 // 长期有效
 const soFar = ref(false)
@@ -207,11 +207,9 @@ watch(
         return
       }
       if (e.noParam) return
-      if (e.key === 'expireTime' && !val[e.key]) return handleSoFarChange(true, e)
-      if (e.key === 'bizId' && val.source === '0') return // 非招聘会职位
+      // if (e.key === 'bizId' && val.source === '0') return // 非招聘会职位
       e.value = val[e.key]
     })
-    // jobFairValid()  // 招聘会回显
   },
   { immediate: true },
   { deep: true }
@@ -261,7 +259,8 @@ const getQuery = async () => {
   if (!valid) return
   const obj = {
     hirePrice: 0,
-    soFar: soFar.value,
+    expireTime: null, // 默认为长期有效
+    bizId: null, // 招聘会id
     hire: false
   }
   items.value.options.forEach(e => {
@@ -269,10 +268,10 @@ const getQuery = async () => {
     else obj[e.key] = e.value
   })
   
-  if (jobFairCheckbox.value && !obj.bizId && jobFairWhiteList.value?.length) {
-    Snackbar.warning('请选择招聘会')
-    return 'failed'
-  }
+  // if (jobFairCheckbox.value && !obj.bizId && jobFairWhiteList.value?.length) {
+  //   Snackbar.warning('请选择招聘会')
+  //   return 'failed'
+  // }
 
   if (!obj.content) {
     Snackbar.warning('请填写岗位职责')
@@ -289,8 +288,9 @@ const getQuery = async () => {
     return 'failed'
   }
   
-  obj.source = jobFairCheckbox.value ? '2' : '0' // 职位来源(0职位管理|1众聘职位|2招聘会)
-  if (obj.source === '0')  obj.bizId = null
+  // obj.source = jobFairCheckbox.value ? '2' : '0' // 职位来源(0职位管理|1众聘职位|2招聘会)
+  obj.source = props.isFair ? '2' : '0' // 职位来源(0职位管理|1众聘职位|2招聘会)
+  // if (obj.source === '0')  obj.bizId = null
 
   Object.assign(query, obj)
   return query

+ 166 - 2
src/views/recruit/enterprise/positionManagement/index.vue

@@ -6,8 +6,9 @@
       </div>
       <div class="text-end">
         <v-btn prepend-icon="mdi-plus" color="primary" @click="handleAdd">新增</v-btn>
+        <v-btn class="mx-3" prepend-icon="mdi-plus" color="primary" @click="handleClone">克隆已有职位发布</v-btn>
         <span>
-          <v-btn :loading="uploadLoading" prepend-icon="mdi-download-box-outline" color="primary" variant="tonal" class="ml-3" @click="handleUploadBefore">
+          <v-btn :loading="uploadLoading" prepend-icon="mdi-download-box-outline" color="primary" variant="tonal" @click="handleUploadBefore">
             职位批量导入
           </v-btn>
           <File ref="uploadFile" :custom="true" customName="multipartFile" accept=".xlsx, .xls" @success="handleUploadPosition"></File>
@@ -38,6 +39,75 @@
           @handleChange="handleChangePage"
         ></CtPagination>
       </div>
+
+      <v-navigation-drawer v-model="showDrawer" location="right" temporary absolute width="600">
+        <Loading :visible="positionLoading" :contained="true"></Loading>
+        <div class="resume-box">
+          <div class="resume-header">
+            <div class="resume-title mr-5">已发布职位</div>
+          </div>
+        </div>
+        <div class="px-3">
+          <v-text-field
+            v-model="positionSearch"
+            append-inner-icon="mdi-magnify"
+            density="compact"
+            :label="t('position.positionName')"
+            variant="outlined"
+            hide-details
+            color="primary"
+            clearable
+            single-line
+            @click:clear="handlePositionChange(1)"
+            @click:append-inner="handlePositionChange(1)"
+            @keyup.enter="handlePositionChange(1)"
+          ></v-text-field>
+        </div>
+        <div class="pa-3" v-if="positionItems.length">
+          <div v-for="val in positionItems" :key="val.id" class="itemBox mb-3">
+            <div>
+              <div class="position" style="padding: 10px 20px;">
+                <div class="d-flex align-center justify-space-between">
+                  <span class="position-name" v-ellipse-tooltip>{{ formatName(val.name) }}</span>
+                </div>
+                <div :class="['mt-1', 'other-info', 'ellipsis']">
+                  <span>{{ val.areaName ? val.area?.str : '全国' }}</span>
+                  <span class="lines" v-if="val.eduName"></span>
+                  <span>{{ val.eduName }}</span>
+                  <span class="lines"></span>
+                  <span>{{ val.expName }}</span>
+                  <span class="lines"></span>
+                  <span v-if="!val.payFrom && !val.payTo">面议</span>
+                  <span v-else>{{ val.payFrom ? val.payFrom + '-' : '' }}{{ val.payTo }}{{ val.payName ? '/' + val.payName : '' }}</span>
+                  <span class="lines" v-if="val.positionName"></span>
+                  <span>{{ val.positionName }}</span>
+                </div>
+              </div>
+              <div
+                class="d-flex align-center"
+                style="padding: 10px 20px; background: linear-gradient(90deg,#f5fcfc,#fcfbfa);"
+                :style="{'justify-content': val.hrName ? 'space-between' : 'flex-end'}"
+              >
+                <div v-if="val.hrName" class="d-flex align-center font-size-14" style="max-width: 70%;">
+                  <v-avatar size="30">
+                    <v-img :src="getUserAvatar(val.hrHeadImg)" />
+                  </v-avatar>
+                  <div class="ml-3" v-ellipse-tooltip style="max-width: calc(100% - 34px);">{{ val.hrName }}</div>
+                </div>
+                <v-btn size="small" color="primary" @click="handleTo(val)">克隆加入</v-btn>
+              </div>
+            </div>
+          </div>
+          <CtPagination
+            v-if="positionTotal"
+            :total="positionTotal"
+            :page="positionPageInfo.pageNo"
+            :limit="positionPageInfo.pageSize"
+            @handleChange="handlePositionChange"
+          ></CtPagination>
+        </div>
+        <Empty v-else :elevation="false"></Empty>
+      </v-navigation-drawer>
     </v-card>
   </div>
 </template>
@@ -56,6 +126,8 @@ import { getEnterprisePubJobTypePermission } from '@/api/recruit/enterprise/posi
 import download from '@/utils/download'
 import Snackbar from '@/plugins/snackbar'
 import Confirm from '@/plugins/confirm'
+import { formatName } from '@/utils/getText'
+import { getUserAvatar } from '@/utils/avatar'
 import { useRoute } from 'vue-router'; const route = useRoute()
 
 const store = useUserStore()
@@ -66,8 +138,8 @@ const tipsText = ref(t('common.noData'))
 const query = ref({
   pageSize: 10,
   pageNo: 1,
+  jobFairId: 0, // 过滤非招聘会职位
   status: 0, // 0招聘中 1已关闭
-  hasExpiredData: false, // true 到期职位
   hire: false // true 众聘岗位
 })
 const templateLoading = ref(false)
@@ -204,7 +276,99 @@ const handleEnter = (e) => {
   query.value.pageNo = 1
   getPositionList()
 }
+
+// 获取所有职位列表(普通职位、往届招聘会职位)
+const positionTotal = ref(0)
+const positionItems = ref([])
+const positionLoading = ref(false)
+const positionSearch = ref('')
+const positionPageInfo = ref({
+  pageNo: 1,
+  pageSize: 10
+})
+const getList = async () => {
+  positionLoading.value = true
+  const query = {
+    ...positionPageInfo.value
+  }
+  if (positionSearch.value) {
+    Object.assign(query, {
+      name: positionSearch.value
+    })
+  }
+  try {
+    const { list, total } = await getJobAdvertisedList(query)
+    positionTotal.value = total
+    positionItems.value = list.length ? dealDictArrayData([], list) : []
+  } finally {
+    positionLoading.value = false
+  }
+}
+
+// 克隆已有职位发布
+const showDrawer = ref(false)
+const handleClone = async () => {
+  positionSearch.value = ''
+  positionPageInfo.value.pageNo = 1
+  getList()
+  showDrawer.value = true
+}
+
+const handlePositionChange = (val) => {
+  positionPageInfo.value.pageNo = val
+  getList()
+}
+
+const handleTo = (val) => {
+  router.push(`/recruit/enterprise/position/edit?id=${val.id}&clone=1`)
+}
 </script>
 
 <style scoped lang="scss">
+.itemBox {
+  position: relative;
+  border: 1px solid #e5e6eb;
+}
+.position-name {
+  color: var(--color-333);
+  font-size: 16px;
+}
+.position {
+  position: relative;
+  .item-select {
+    position: absolute;
+    left: -8px;
+    top: -13px;
+  }
+}
+.lines {
+  display: inline-block;
+  width: 1px;
+  height: 17px;
+  vertical-align: middle;
+  background-color: #e0e0e0;
+  margin: 0 10px;
+}
+.other-info {
+  font-size: 13px;
+  color: var(--color-666);
+}
+.bottom {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  width: 100%;
+  height: 30px;
+  background-color: #f7f8fa;
+  font-size: 14px;
+  color: var(--color-888);
+
+  &-item {
+    width: 33.3%;
+    max-width: 33.3%;
+  }
+}
+.actions:hover {
+  color: var(--v-primary-base) !important;
+}
 </style>

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

@@ -143,8 +143,8 @@ onMounted(() => {
 		}
 		// 招聘会数据获取
 		if (k.key === 'jobFairId') {
-			const data = await getJobFairList()
-			k.items = data.map(e => {
+			const data = await getJobFairList({ pageNo: 1, pageSize: 100 })
+			k.items = data.list.map(e => {
 				return { label: e.title.replace(/<\/?p[^>]*>/gi, ''), value: e.id }
 			})
 			if (props.jobFairId) {

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

@@ -274,7 +274,7 @@ const handleClear = () => {
 const jobNum = ref(0)
 const positionList = ref([])
 const getJobList = async () => {
-  const { total, list } = await getJobAdvertisedList({ pageNo: 1, pageSize: 10, hasExpiredData: false, status: 0 })
+  const { total, list } = await getJobAdvertisedList({ pageNo: 1, pageSize: 10, jobFairId: 0, status: 0 })
   jobNum.value = total
   if (!list?.length) {
     positionList.value = []

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

@@ -99,7 +99,7 @@ getCvDetail()
 // 职位列表
 const jobNum = ref(0)
 const getJobList = async () => {
-  const { total } = await getJobAdvertisedList({ pageNo: 1, pageSize: 10, hasExpiredData: false, status: 0 })
+  const { total } = await getJobAdvertisedList({ pageNo: 1, pageSize: 10, status: 0 })
   jobNum.value = total
 }
 getJobList()