Procházet zdrojové kódy

全员猎聘拆分

Xiao_123 před 8 měsíci
rodič
revize
6cbdf05b30

+ 0 - 1
src/layout/enterprise.vue

@@ -44,7 +44,6 @@ const key = computed(() => {
 
 const whiteList = [
   '/recruit/enterprise/resumeManagement/talentPool/details/details',
-  '/recruit/enterprise/position/details',
   '/recruit/enterprise/purchasePackage',
   '/recruit/enterprise/systemManagement/groupAccount/invite/0',
   '/recruit/enterprise/systemManagement/groupAccount/invite/1'

+ 33 - 27
src/router/modules/components/recruit/enterprise.js

@@ -63,7 +63,7 @@ const enterprise = [
     children: [
       {
         path: '/recruit/enterprise/position',
-        name: 'recruitEnterprisePosition',
+        show: true,
         meta: {
           title: '职位列表',
           enName: 'Job list'
@@ -85,15 +85,42 @@ const enterprise = [
           title: '职位编辑'
         },
         component: () => import('@/views/recruit/enterprise/positionManagement/components/add.vue')
+      }
+    ]
+  },
+  {
+    path: '/recruit/enterprise/hirePosition',
+    component: Layout,
+    name: 'crowdSourcing',
+    meta: {
+      title: '全员猎聘',
+      enName: 'Crowd Sourcing',
+      icon: 'mdi-account-star-outline'
+    },
+    children: [
+      {
+        path: '/recruit/enterprise/hirePosition',
+        show: true,
+        meta: {
+          title: '全员猎聘'
+        },
+        component: () => import('@/views/recruit/enterprise/hirePosition/index.vue')
       },
       {
-        path: '/recruit/enterprise/position/details/:id',
+        path: '/recruit/enterprise/hirePosition/add',
         show: true,
         meta: {
-          title: '职位详情',
-          hideSide: true
+          title: '新增职位'
         },
-        component: () => import('@/views/recruit/enterprise/positionManagement/components/details.vue')
+        component: () => import('@/views/recruit/enterprise/hirePosition/components/add.vue')
+      },
+      {
+        path: '/recruit/enterprise/hirePosition/edit',
+        show: true,
+        meta: {
+          title: '职位编辑'
+        },
+        component: () => import('@/views/recruit/enterprise/hirePosition/components/add.vue')
       }
     ]
   },
@@ -288,27 +315,6 @@ const enterprise = [
       }
     ]
   },
-  // {
-  //   path: '/recruit/enterprise/enterpriseCenter',
-  //   component: Layout,
-  //   name: 'enterpriseCenter',
-  //   show: true,
-  //   redirect: '/recruit/enterprise/enterpriseCenter',
-  //   meta: {
-  //     title: '企业中心'
-  //   },
-  //   children: [
-  //     {
-  //       path: '/recruit/enterprise/enterpriseCenter',
-  //       show: true,
-  //       component: () => import('@/views/recruit/enterprise/enterpriseCenter/index.vue'),
-  //       meta: {
-  //         title: '企业中心',
-  //         hideSide: true
-  //       }
-  //     }
-  //   ]
-  // },
   {
     path: '/recruit/enterprise/purchasePackage',
     component: Layout,
@@ -327,6 +333,6 @@ const enterprise = [
         },
       }
     ]
-  },
+  }
 ]
 export default enterprise

+ 134 - 0
src/views/recruit/enterprise/hirePosition/components/add.vue

@@ -0,0 +1,134 @@
+<template>
+  <div>
+    <v-card class="pa-5">
+      <v-timeline align="start" side="end">
+        <v-timeline-item
+          v-for="(val, i) in list"
+          :key="i"
+          :dot-color="val.color"
+          :icon="val.icon"
+        >
+          <div>
+            <h2 class="mt-n1 headline font-weight-regular">{{ val.title }}</h2>
+            <div class="mb-4 desc">{{ val.desc }}</div>
+            <component class="mt-10" :is="val.path" :ref="val.ref" :itemData="itemData"></component>
+          </div>
+        </v-timeline-item>
+      </v-timeline>
+      <div class="text-center mb">
+        <v-btn class="half-button mr-3" color="primary" variant="outlined" @click="handleCancel()">{{ $t('common.cancel') }}</v-btn>
+        <v-btn class="half-button" color="primary" @click="handleSave">{{ $t('common.release') }}</v-btn>
+      </div>
+    </v-card>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'enterprise-position-add'})
+import { ref } from 'vue'
+import { useRouter, useRoute } from 'vue-router'
+import { dealDictObjData } from '@/utils/position'
+import { saveJobAdvertised, getJobDetails, createTradeOrder } from '@/api/position'
+import baseInfo from './baseInfo.vue'
+import jobRequirements from './jobRequirements.vue'
+import Snackbar from '@/plugins/snackbar'
+import { useI18n } from '@/hooks/web/useI18n'
+import { useUserStore } from '@/store/user'
+
+const { t } = useI18n()
+const route = useRoute()
+const router = useRouter()
+const userStore = useUserStore()
+const baseInfoRef = ref()
+const jobRequirementsRef = ref()
+const itemData = ref({})
+
+const list = [
+  {
+    color: '#00897B',
+    icon: 'mdi-numeric-1',
+    title: t('position.positionInformation'),
+    desc: '',
+    path: baseInfo,
+    ref: baseInfoRef
+  },
+  {
+    color: 'indigo-lighten-2',
+    icon: 'mdi-numeric-2',
+    title: t('position.jobRequirements'),
+    desc: t('position.requirementDesc'),
+    path: jobRequirements,
+    ref: jobRequirementsRef
+  }
+]
+
+
+let submitParams = {}
+// 发布
+const handleSave = async () => {
+  const baseInfo = await baseInfoRef.value[0].getQuery()
+  if (baseInfo === 'failed') return
+  const requirement = await jobRequirementsRef.value[0].getQuery()
+  if (!baseInfo || !requirement) return Snackbar.warning('请将信息填写完整')
+  
+  submitParams = Object.assign(baseInfo, requirement, { currency_type: 0 }) // currency_type: 写死0(人民币)
+  if (route.query && route.query.id) submitParams.id = route.query.id // 有id则为编辑
+
+  if (!baseInfo.hirePrice) return Snackbar.warning('请填写点数!')
+  saveEmit(submitParams) // 正常发布,到列表中发起支付(暂定解决方案)
+}
+
+const saveEmit = async () => {
+  try {
+    const res = await saveJobAdvertised(submitParams)
+    Snackbar.success(submitParams.id ? t('common.editSuccessMsg') : t('common.publishSuccessMsg1'))
+    // 生成订单
+    await createTradeOrder({
+      spuId: res,
+      spuName: submitParams.name,
+      price: submitParams.hirePrice,
+      type: 2 // 发布众聘职位订单
+    })
+    handleCancel()
+  } catch (error) {
+    console.log('error', error)
+  }
+}
+
+// 获取编辑的职位详情
+const getPositionDetail = async (id) => {
+  const data = await getJobDetails({ id })
+  if (!data && !Object.keys(data).length) return
+  itemData.value = {...data, ...dealDictObjData({}, data)}
+}
+
+// 有id为编辑
+if (route.query && route.query.id) {
+  if (route.query.id) getPositionDetail(route.query.id)
+}
+
+// 取消
+const handleCancel = () => {
+  itemData.value = {}
+  router.push({ path: '/recruit/enterprise/hirePosition' })
+  // 新增职位发布需更新账户信息
+  if (route.query && !route.query?.id) {
+    setTimeout(async () => {
+      await userStore.getEnterpriseUserAccountInfo()
+    }, 2000)
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.desc {
+  font-size: 13px;
+  color: var(--color-666);
+}
+</style>
+
+<style lang="scss" scoped>
+.mb {
+  margin-bottom: 100px;
+}
+</style>

+ 248 - 0
src/views/recruit/enterprise/hirePosition/components/baseInfo.vue

@@ -0,0 +1,248 @@
+<template>
+  <div>
+    <CtForm ref="formPageRef" :items="items" style="width: 650px;">
+      <template #numericalValue>
+        <div class="font-size-14 color-error my-1">
+          <div class="d-flex align-center font-size-13">
+          <div style="color: var(--v-error-base); cursor: pointer; text-decoration: underline;" @click="handleViewRule">
+            <v-icon size="20" color="error">mdi-help-circle-outline</v-icon>
+            众聘岗位规则说明。
+          </div>
+          <div class=" ml-5" style="color: var(--v-error-base);">
+            众聘岗位分配比例:推荐人占比{{ ratio.recommendRate }}%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 平台占比{{ ratio.headhuntRate }}%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 投递人占比{{ ratio.cvRate }}%
+          </div>
+        </div>
+          <div class="d-flex">
+            按众聘岗位分配比例计算后的赏金: 
+            <span class="calculation ml-3">推荐人{{ calculation('hirePrice', 1, true) }}元</span>
+            <span class="calculation">平台{{ calculation('hirePrice', 0, true) }}元</span>
+            <span class="calculation">投递人{{ calculation('hirePrice', 2, true) }}元</span>
+          </div>
+        </div>
+      </template>
+      <template #positionId="{ item }">
+        <v-menu :close-delay="1" :open-delay="0" v-bind="$attrs">
+          <template v-slot:activator="{  props }">
+            <textUI
+              :modelValue="item.value"
+              :item="item"
+              v-bind="props"
+              style="position: relative;"
+            ></textUI>
+          </template>
+          <jobTypeCard class="jobTypeCardBox" :select="[query.positionId].filter(Boolean)" :isSingle="true" @handleJobClick="handleJobClickItem"></jobTypeCard>
+        </v-menu>
+        <v-btn class="ml-3 half-button" color="primary" style="margin-top: 2px;" @click="useJobTemplate(item)">岗位模板</v-btn>
+      </template>
+    </CtForm>
+
+    <CtDialog :visible="show" :widthType="1" titleClass="text-h6" title="众聘岗位规则说明" :footer="false" @close="show = false">
+      <RulePage />
+    </CtDialog>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'position-add-baseInfo'})
+import CtForm from '@/components/CtForm'
+import { reactive, ref, watch } from 'vue'
+import textUI from '@/components/FormUI/TextInput'
+import jobTypeCard from '@/components/jobTypeCard'
+import RulePage from './rule.vue'
+import { commissionCalculation } from '@/utils/position'
+import { getPublicRatio, getRecruitPositionDetails } from '@/api/recruit/enterprise/position'
+
+const props = defineProps({
+  itemData: Object
+})
+
+const show = ref(false)
+const ratio = ref({})
+
+// 按分配比例计算金额积分
+const calculation = (key, type) => {
+  const value = items.value.options.find(e => e.key === key).value
+  return commissionCalculation(value, type)
+}
+
+const getValue = (key) => {
+  return items.value.options.find(e => e.key === key)
+}
+const formPageRef = ref()
+let query = reactive({})
+
+const items = ref({
+  options: [
+    {
+      type: 'number',
+      key: 'hirePrice',
+      value: null,
+      label: '请填写点数 (1赏金=10点数,点数填入不得少于10且为10的倍数)',
+      suffix: '点',
+      hideDetails: true,
+      change: val => hirePriceChange(val, 'hirePrice')
+    },
+    {
+      slotName: 'numericalValue',
+      noParam: true,
+    },
+    {
+      type: 'text',
+      key: 'name',
+      value: '',
+      label: '职位名称 *',
+      rules: [v => !!v || '请填写职位名称']
+    },
+    {
+      slotName: 'positionId',
+      key: 'positionId',
+      value: '',
+      labelKey: 'positionName',
+      label: '职位类型 *',
+      noParam: true,
+      readonly: true,
+      rules: [v => !!v || '请选择职位类型']
+    },
+    {
+      type: 'wangEditor',
+      key: 'content',
+      value: '',
+      label: '岗位职责 *',
+      maxLength: 5000,
+      rules: '请填写岗位职责'
+    },
+    {
+      type: 'wangEditor',
+      key: 'requirement',
+      value: '',
+      label: '岗位要求 *',
+      maxLength: 5000,
+      rules: '请填写岗位要求'
+    }
+  ]
+})
+
+
+// 获取众聘分配比例
+const getRatio = async () => {
+  const data = await getPublicRatio()
+  ratio.value = data
+}
+getRatio()
+
+// 编辑回显
+watch(
+  () => props.itemData,
+  (val) => {
+    if (!Object.keys(val).length) return
+    items.value.options.forEach(e => {
+      if (e.labelKey) {
+        query[e.key] = val[e.key]
+        e.value = val[e.labelKey]
+        return
+      }
+      if (e.noParam) return
+      e.value = val[e.key]
+      e.change && e.change(e.value)
+    })
+  },
+  { immediate: true },
+  { deep: true }
+)
+
+// 职位类型
+const handleJobClickItem = (list, name) => {
+  const positionId = getValue('positionId')
+  if (!list.length) {
+    delete query.positionId
+    positionId.value = ''
+    return
+  }
+  query.positionId = list[0]
+  positionId.value = name
+}
+
+// 岗位模板
+import Confirm from '@/plugins/confirm'
+import Snackbar from '@/plugins/snackbar'
+import { useI18n } from '@/hooks/web/useI18n'; const { t } = useI18n()
+const useJobTemplate = async () => {
+  if (!query.positionId) return Snackbar.warning('请先选择职位类型')
+  // 获取职位模板内容-赋值
+  const res = await getRecruitPositionDetails(query.positionId)
+  if (!res || !res.content || !res.requirement) return Snackbar.warning('此职位类型没有可使用的模板!')
+  const content =  items.value.options.find(e => e.key === 'content')
+  const requirement =  items.value.options.find(e => e.key === 'requirement')
+  if ((content && content.value) || (requirement && requirement.value)) {
+    // 弹窗提示
+    Confirm(t('common.confirmTitle'), '您确定要放弃目前岗位描述的内容吗?').then(() => {
+      content.value = res.content
+      requirement.value = res.requirement
+      Snackbar.success('模板填充完成!')
+    })
+  } else {
+    // 无内容点击默认填充
+    if (content) content.value = res.content
+    if (requirement) requirement.value = res.requirement
+    Snackbar.success('模板填充完成!')
+  }
+}
+
+// 众聘规则查看
+const handleViewRule = () => {
+  show.value = true
+}
+
+// 众聘规则查看
+const hirePriceChange = (value, key) => {
+  let calcCost = value-0
+  if (calcCost < 10 ) calcCost = 10
+  else {
+    calcCost = parseInt(calcCost/10)*10
+  }
+  const obj = items.value.options.find(k => k.key === key)
+  if (obj) {
+    obj.value = calcCost
+  }
+}
+
+const getQuery = async () => {
+  const { valid } = await formPageRef.value.formRef.validate()
+  if (!valid) return
+  const obj = {
+    hire: true
+  }
+  items.value.options.forEach(e => {
+    if (e.noParam) return
+    else obj[e.key] = e.value
+  })
+  if (!obj.content) {
+    Snackbar.warning('请填写岗位职责')
+    return 'failed'
+  }
+  if (!obj.requirement) {
+    Snackbar.warning('请填写岗位要求')
+    return 'failed'
+  }
+  
+  query = Object.assign(query, obj)
+  return query
+}
+
+defineExpose({
+  formPageRef,
+  getQuery
+})
+</script>
+
+<style scoped lang="scss">
+.jobTypeCardBox {
+  position: absolute;
+  top: -22px;
+  left: 0;
+}
+.calculation {
+  display: block;
+  width: 120px;
+}
+</style>

+ 152 - 0
src/views/recruit/enterprise/hirePosition/components/item.vue

@@ -0,0 +1,152 @@
+<template>
+  <div>
+    <div v-for="val in items" :key="val.id" class="itemBox mb-3" style="height: 162px;">
+      <div class="d-flex justify-space-between" style="padding: 10px 20px;">
+        <div class="position">
+          <div :class="['d-flex' ,'align-center']">
+            <svg-icon class="mr-3" name="pin" size="25"></svg-icon>
+            <span v-if="val.name.indexOf('style')" v-html="val.name" class="position-name"></span>
+            <span v-else class="position-name">{{ val.name }}</span>
+          </div>
+          <div :class="['mt-3', 'other-info', 'ellipsis']">
+            <span>{{ val.areaName }}</span>
+            <span class="lines" v-if="val.areaName && val.eduName"></span>
+            <span>{{ val.eduName }}</span>
+            <span class="lines"></span>
+            <span>{{ val.expName }}</span>
+            <span class="lines"></span>
+            <span>{{ val.payFrom }}-{{ val.payTo }}/{{ val.payName }}</span>
+            <span class="lines"></span>
+            <span>{{ val.positionName }}</span>
+          </div>
+          <div v-if="val?.hire" class="mt-2">
+            <v-chip v-if="val?.hirePrice && val.hirePrice > 0" class="mr-3" label color="primary" size="small">赏金:{{ commissionCalculation(val.hirePrice, 1) }}元</v-chip>
+          </div>
+        </div>
+        <div class="d-flex align-center">
+          <v-chip v-if="(val.status-0) === 99" color="warning" label>职位待发布,支付后成功后自动发布</v-chip>
+          <v-chip v-if="val.status === '1'" color="error" class="cursor-pointer" label  style="text-decoration: underline;" @click="handleAction(1, val)">职位已关闭,点击激活职位</v-chip>
+        </div>
+      </div>
+      <div class="bottom pa-5 d-flex justify-space-between align-center">
+        <div>{{ $t('position.refreshTime') }} :{{ timesTampChange(val.updateTime, 'Y-M-D') }}</div>
+        <div class="d-flex align-center">
+          <span v-if="(val.status-0) === 99" class="cursor-pointer color-primary" @click="toPay(val)">去支付</span>
+          <span v-if="(val.status-0) === 99" class="lines"></span>
+          <span v-if="(val.status - 0) !== 99 && val.status !== '1'" class="cursor-pointer actions" @click="handleAction(0, val)">{{ $t('common.close') }}</span>
+          <span v-if="(val.status - 0) !== 99 && val.status !== '1'" class="lines"></span>
+          <span class="cursor-pointer" @click="handleEdit(val)">编辑</span>
+        </div>
+      </div>
+    </div>
+  </div>
+  <confirmPaymentDialog
+    v-if="showConfirmPaymentDialog"
+    :cost="cost"
+    :spuId="spuId"
+    :spuName="spuName"
+    :orderType="2"
+    @paySuccess="paySuccess"
+    @close="showConfirmPaymentDialog = false"
+  ></confirmPaymentDialog>
+</template>
+
+<script setup>
+import { commissionCalculation } from '@/utils/position'
+defineOptions({ name: 'enterprise-position-item'})
+import { ref } from 'vue'
+import { useRouter } from 'vue-router'
+import { timesTampChange } from '@/utils/date'
+import { useI18n } from '@/hooks/web/useI18n'
+import { closeJobAdvertised, enableJobAdvertised } from '@/api/position'
+import Snackbar from '@/plugins/snackbar'
+import confirmPaymentDialog from '@/components/pay/confirmPaymentDialog.vue'
+
+const { t } = useI18n()
+const emit = defineEmits(['refresh'])
+defineProps({
+  items: Array
+})
+
+const showConfirmPaymentDialog = ref(false)
+const cost = ref(0)
+const spuId = ref('')
+const spuName = ref('')
+const toPay = (val) => {
+  spuId.value = val.id || ''
+  spuName.value = val.name || ''
+  cost.value = val.hirePrice
+  // 打开支付弹窗
+  showConfirmPaymentDialog.value = true
+}
+// 支付成功
+const paySuccess = async () => {
+  showConfirmPaymentDialog.value = false
+  setTimeout(() => {
+    emit('refresh')
+  }, 1000)
+}
+
+
+const apiList = [ closeJobAdvertised, enableJobAdvertised ]
+
+// 职位关闭、激活
+const handleAction = async (index, { id }) => {
+  const ids = [id]
+  if (!ids.length && !index) return
+  await apiList[index](ids)
+  Snackbar.success(t('common.operationSuccessful'))
+  emit('refresh')
+}
+
+const router = useRouter()
+// 职位编辑
+const handleEdit = (val) => {
+  router.push(`/recruit/enterprise/hirePosition/edit?id=${val.id}`)
+}
+</script>
+
+<style scoped lang="scss">
+.itemBox {
+  position: relative;
+  border: 1px solid #e5e6eb;
+}
+.position-name {
+  color: var(--color-333);
+  font-size: 19px;
+}
+.position {
+  max-width: 46%;
+  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>

+ 324 - 0
src/views/recruit/enterprise/hirePosition/components/jobRequirements.vue

@@ -0,0 +1,324 @@
+<template>
+  <div style="width: 100%;">
+    <CtForm ref="formPageRef" :items="items" style="width: 600px;">
+      <template #tagList>
+        <div>
+          <span style="color: #797474;">职位关键字:</span>
+          <v-chip class="cursor-pointer mr-2 mb-3" color="primary" variant="outlined" @click="show = true; select = tag">
+            <v-icon>mdi-plus</v-icon>
+          </v-chip>
+          <v-chip v-for="(val, index) in tag" :key="index" class="mr-2 mb-3" color="primary">
+            {{ val }}
+          </v-chip>
+        </div>
+      </template>
+    </CtForm>
+  </div>
+
+  <CtDialog :visible="show" :widthType="3" titleClass="text-h6" title="职位关键字选择" :footer="true" @close="show = false" @submit="handleSubmit">
+    <div>已选中关键字:</div>
+    <div v-if="select.length">
+      <v-chip
+        v-for="(item, index) in select" :key="index"
+        class="chip mr-2 mt-4"
+        label color="#ea8d03"
+      >
+        {{ item }}
+        <v-icon size="18" color="#ea8d03" style="margin-left: 6px;" @click="handleCancelSelect(item)">mdi-close-circle</v-icon>
+      </v-chip>
+    </div>
+    <v-divider class="my-5"></v-divider>
+    <div v-for="val in tagList" :key="val.id" class="mb-8">
+      <span style="font-size: 16px;">{{ val?.nameCn || '--' }}</span>
+      <div v-if="val?.children?.length">
+        <v-chip 
+          v-for="k in val.children" 
+          :key="k.id"
+          class="mx-2 mt-4 cursor-pointer"
+          :text="k.nameCn"
+          variant="outlined"
+          color="primary"
+          :value="k.id"
+          label
+          :disabled="select.includes(k.nameCn)"
+          @click="handleSelect(k.nameCn)"
+        >
+          <v-icon icon="mdi-plus" start></v-icon>
+          {{ k?.nameCn || '--' }}
+        </v-chip>
+      </div>
+    </div>
+  </CtDialog>
+</template>
+
+<script setup>
+defineOptions({ name: 'position-add-job-requirements'})
+import CtForm from '@/components/CtForm'
+import { reactive, ref, watch } from 'vue'
+import { getDict } from '@/hooks/web/useDictionaries'
+import { cityToProvince } from '@/utils/areaDeal'
+import { getTagTreeDataApi } from '@/api/enterprise'
+
+const props = defineProps({
+  itemData: Object
+})
+
+const formPageRef = ref()
+let query = reactive({})
+const items = ref({
+  options: [
+    {
+      type: 'autocomplete',
+      key: 'type',
+      value: null,
+      label: '招聘类型 *',
+      itemText: 'label',
+      itemValue: 'value',
+      col: 4,
+      dictTypeName: 'menduner_job_type',
+      rules: [v => !!v || '招聘类型'],
+      items: []
+    },
+    {
+      type: 'autocomplete',
+      key: 'eduType',
+      value: null,
+      label: '最高学历 *',
+      itemText: 'label',
+      itemValue: 'value',
+      col: 4,
+      flexStyle: 'mx-3',
+      dictTypeName: 'menduner_education_type',
+      rules: [v => !!v || '请选择最高学历'],
+      items: []
+    },
+    {
+      type: 'autocomplete',
+      key: 'expType',
+      value: null,
+      label: '工作经验 *',
+      itemText: 'label',
+      itemValue: 'value',
+      col: 4,
+      dictTypeName: 'menduner_exp_type',
+      rules: [v => !!v || '请选择工作经验'],
+      items: []
+    },
+    {
+      type: 'text',
+      key: 'payFrom',
+      value: '',
+      col: 4,
+      label: '最低薪资 *',
+      suffix: '元',
+      rules: [
+        value => {
+          if (value) return true
+          return '请填写最低薪资'
+        },
+        value => {
+          if (value >= 1) return true
+          return '数额不得小于1'
+        }
+      ]
+    },
+    {
+      type: 'text',
+      key: 'payTo',
+      value: '',
+      col: 4,
+      label: '最高薪资 *',
+      flexStyle: 'mx-3',
+      suffix: '元',
+      rules: [
+        value => {
+          if (value) return true
+          return '请填写最高薪资'
+        },
+        value => {
+          if (value >= 1) return true
+          return '数额不得小于1'
+        }
+      ]
+    },
+    {
+      type: 'autocomplete',
+      key: 'payUnit',
+      value: null,
+      label: '薪酬单位 *',
+      itemText: 'label',
+      itemValue: 'value',
+      col: 4,
+      dictTypeName: 'menduner_pay_unit',
+      rules: [v => !!v || '请选择薪酬单位'],
+      items: []
+    },
+    {
+      type: 'autocomplete',
+      key: 'workAreaProvinceId',
+      value: null,
+      label: '工作城市:省 *',
+      outlined: true,
+      itemText: 'name',
+      itemValue: 'id',
+      returnSelect: true,
+      noParam: true,
+      col: 6,
+      flexStyle: 'mr-3',
+      rules: [v => !!v || '请选择工作城市:省'],
+      items: [],
+      change: null
+    },
+    {
+      type: 'autocomplete',
+      key: 'areaId',
+      value: null,
+      label: '工作城市:市 *',
+      outlined: true,
+      itemText: 'name',
+      itemValue: 'id',
+      col: 6,
+      rules: [v => !!v || '请选择工作城市:市'],
+      items: [],
+      change: null
+    },
+    {
+      type: 'text',
+      key: 'address',
+      value: '',
+      label: '详情地址 *',
+      rules: [v => !!v || '请填写详细地址'],
+    },
+    {
+      slotName: 'tagList',
+      key: 'tagList',
+      value: []
+    }
+  ]
+})
+
+// 招聘职位标签
+const tagList = ref([])
+const getTagList = async () => {
+  const data = await getTagTreeDataApi({ type: 2 })
+  tagList.value = data
+}
+getTagList()
+
+// 获取字典内容
+const getDictData = async () => {
+  items.value.options.forEach(async (e) => {
+    if (e.dictTypeName) {
+      const { data } = await getDict(e.dictTypeName)
+      e.items = data
+    }
+  })
+}
+
+const provinceChange = (value, val, obj) => {
+  const item = items.value.options.find(e => e.key === 'areaId')
+  if (!item) return
+  item.items = obj.children || []
+  item.value = null
+}
+getDict('areaTreeData', null, 'areaTreeData').then(({ data }) => {
+  data = data?.length && data || []
+  if (!data?.length) return console.error('areaTreeData获取失败!')
+  // const china = data.find(e => e.id === '1')
+  // const chinaTreeData = china?.children?.length ? china.children : []
+  const chinaTreeData = data
+  //
+  if (!chinaTreeData?.length) return console.error('chinaTreeData获取失败!')
+  const item = items.value.options.find(e => e.key === 'workAreaProvinceId')
+  if (item?.items) {
+    item.items = chinaTreeData
+    item.change = provinceChange
+  }
+})
+
+const getQuery = async () => {
+  const { valid } = await formPageRef.value.formRef.validate()
+  if (!valid) return
+  const obj = {}
+  items.value.options.forEach(e => {
+    if (e.noParam) return
+    if (e.key === 'tagList') {
+      obj[e.key] = tag.value.length ? tag.value : []
+    }
+    else obj[e.key] = e.value
+  })
+  query = Object.assign(query, obj)
+  return query
+}
+
+// 编辑回显
+watch(
+  () => props.itemData,
+  async (val) => {
+    await getDictData()
+    if (!Object.keys(val).length) return
+    // 编辑
+    let workAreaId = ''
+    items.value.options.forEach(e => {
+      if (e.labelKey) {
+        query[e.key] = val[e.key]
+        e.value = val[e.labelKey]
+        return
+      }
+      if (e.noParam) return
+      e.value = val[e.key]
+      if (e.key === 'areaId' && val[e.key]) workAreaId = val[e.key]
+      if (e.key === 'tagList' && val[e.key] && val[e.key].length) {
+        tag.value = val[e.key] && val[e.key].length ? val[e.key] : []
+      }
+    })
+    if (workAreaId) { // 省份回显
+      const province = items.value.options.find(pv => pv.key === 'workAreaProvinceId')
+      if (province) {
+        const dealReturnObj = await cityToProvince(workAreaId, {}, province.items || [])
+        const city = items.value.options.find(pv => pv.key === 'areaId')
+        if (city) city.items = dealReturnObj.cityList || []
+        province.value = dealReturnObj.pid || ''
+      }
+    }
+  },
+  { immediate: true },
+  { deep: true }
+)
+
+
+// 标签选择
+const show = ref(false)
+const select = ref([])
+const tag = ref([])
+
+// 选择
+const handleSelect = (nameCn) => {
+  const result = select.value.includes(nameCn)
+  if (!result) return select.value.push(nameCn)
+  else select.value = select.value.filter(e => e !== nameCn)
+}
+
+const handleSubmit = () => {
+  tag.value = select.value
+  show.value = false
+}
+
+// 取消选中
+const handleCancelSelect = (nameCn) => {
+  select.value = select.value.filter(e => e !== nameCn)
+}
+
+defineExpose({
+  formPageRef,
+  getQuery
+})
+</script>
+
+<style scoped lang="scss">
+.jobTypeCardBox {
+  position: absolute;
+  top: -22px;
+  left: 0;
+}
+</style>

+ 0 - 0
src/views/recruit/enterprise/positionManagement/components/rule.vue → src/views/recruit/enterprise/hirePosition/components/rule.vue


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

@@ -0,0 +1,96 @@
+<template>
+  <div>
+    <v-card class="card-box pa-5">
+      <div class="d-flex justify-center mt-3">
+        <TextUI :item="textItem" @enter="handleEnter" @appendInnerClick="handleEnter"></TextUI>
+      </div>
+      <div class="text-end">
+        <v-btn prepend-icon="mdi-plus" color="primary" @click="handleAdd">{{ $t('position.newPositionsAdded') }}</v-btn>
+        <v-btn prepend-icon="mdi-export-variant" color="primary" variant="tonal" class="ml-3" @click="handleExport">职位列表下载</v-btn>
+      </div>
+      
+      <div class="mt-3">
+        <PositionItem v-if="items.length" :items="items" @refresh="getPositionList"></PositionItem>
+        <Empty v-if="!items.length" :message="tipsText" :elevation="false"></Empty>
+        <CtPagination
+          v-else
+          :total="total"
+          :page="query.pageNo"
+          :limit="query.pageSize"
+          @handleChange="handleChangePage"
+        ></CtPagination>
+      </div>
+    </v-card>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'enterprise-hire-position-list'})
+import { ref } from 'vue'
+import TextUI from '@/components/FormUI/TextInput'
+import PositionItem from './components/item.vue'
+import { useRouter } from 'vue-router'; const router = useRouter()
+import { getJobAdvertisedList, getJobAdvertisedExport } from '@/api/position'
+import { dealDictArrayData } from '@/utils/position'
+import { useI18n } from '@/hooks/web/useI18n'
+import { useUserStore } from '@/store/user'
+import download from '@/utils/download'
+
+const store = useUserStore()
+
+const { t } = useI18n()
+const total = ref(0)
+const tipsText = ref(t('common.noData'))
+const query = ref({
+  pageSize: 10,
+  pageNo: 1,
+  hire: true
+})
+
+const items = ref([])
+const textItem = ref({
+  type: 'text',
+  width: 600,
+  value: '',
+  label: '请输入职位名称',
+  clearable: true,
+  appendInnerIcon: 'mdi-magnify'
+})
+
+const handleAdd = async () => {
+  router.push('/recruit/enterprise/hirePosition/add')
+  await store.getEnterpriseUserAccountInfo()
+}
+
+const handleExport = async () => {
+  const data = await getJobAdvertisedExport(query.value)
+  download.excel(data, '众聘职位列表')
+}
+
+
+// 获取职位列表
+const getPositionList = async () => {
+  const { list, total: number } = await getJobAdvertisedList(query.value)
+  if (!list.length) {
+    if (query.value.name) tipsText.value = '暂无数据,请更换关键词后再试'
+  }
+  total.value = number
+  items.value = list.length ? dealDictArrayData([], list) : []
+}
+getPositionList()
+
+const handleChangePage = (index) => {
+  query.value.pageNo = index
+  getPositionList()
+}
+
+// 职位名称检索
+const handleEnter = (e) => {
+  query.value.name = e
+  query.value.pageNo = 1
+  getPositionList()
+}
+</script>
+
+<style scoped lang="scss">
+</style>

+ 8 - 32
src/views/recruit/enterprise/positionManagement/components/add.vue

@@ -16,7 +16,7 @@
         </v-timeline-item>
       </v-timeline>
       <div class="text-center mb">
-        <v-btn class="half-button mr-3" color="primary" variant="outlined" @click="handleCancel(itemData.hire)">{{ $t('common.cancel') }}</v-btn>
+        <v-btn class="half-button mr-3" color="primary" variant="outlined" @click="handleCancel()">{{ $t('common.cancel') }}</v-btn>
         <v-btn class="half-button" color="primary" @click="handleSave">{{ $t('common.release') }}</v-btn>
       </div>
     </v-card>
@@ -28,7 +28,7 @@ defineOptions({ name: 'enterprise-position-add'})
 import { ref } from 'vue'
 import { useRouter, useRoute } from 'vue-router'
 import { dealDictObjData } from '@/utils/position'
-import { saveJobAdvertised, getJobDetails, createTradeOrder } from '@/api/position'
+import { saveJobAdvertised, getJobDetails } from '@/api/position'
 import baseInfo from './baseInfo.vue'
 import jobRequirements from './jobRequirements.vue'
 import Snackbar from '@/plugins/snackbar'
@@ -74,37 +74,14 @@ const handleSave = async () => {
   submitParams = Object.assign(baseInfo, requirement, { currency_type: 0 }) // currency_type: 写死0(人民币)
   if (route.query && route.query.id) submitParams.id = route.query.id // 有id则为编辑
 
-  // 判断是否有选择众聘岗位,选择了是否有填赏金或积分
-  if (!baseInfo?.hire) {
-    saveEmit(submitParams)
-  } else {
-    if (!baseInfo.hirePrice) return Snackbar.warning('您选择的是众聘岗位,请填写点数!') // 或积分
-    // if (Number(baseInfo.hirePrice) === 0 && Number(baseInfo.hirePoint) === 0) return Snackbar.warning('填写的赏金不得小于10')
-    // const point = JSON.parse(localStorage.getItem('enterpriseUserAccount'))?.point
-    // if (Number(baseInfo.hirePoint) && (Number(baseInfo.hirePoint) > Number(point))) {
-    //   // 积分不足
-    //   return Snackbar.warning('您所剩的积分不足,请修改奖励的积分!')
-    // }
-    saveEmit(submitParams) // 正常发布,到列表中发起支付(暂定解决方案)
-    // needPrice.value = Number(baseInfo.hirePrice)
-    // // 生成订单
-  }
+  saveEmit(submitParams)
 }
 
 const saveEmit = async () => {
   try {
-    const res = await saveJobAdvertised(submitParams)
-    Snackbar.success(submitParams.id ? t('common.editSuccessMsg') : submitParams.hire ? t('common.publishSuccessMsg1') : t('common.publishSuccessMsg'))
-    if (submitParams.hire) {
-      // 生成订单
-      await createTradeOrder({
-        spuId: res,
-        spuName: submitParams.name,
-        price: submitParams.hirePrice,
-        type: 2, // 发布众聘职位订单
-      })
-    }
-    handleCancel(submitParams.hire)
+    await saveJobAdvertised(submitParams)
+    Snackbar.success(submitParams.id ? t('common.editSuccessMsg') : t('common.publishSuccessMsg'))
+    handleCancel()
   } catch (error) {
     console.log('error', error)
   }
@@ -121,12 +98,11 @@ const getPositionDetail = async (id) => {
 if (route.query && route.query.id) {
   if (route.query.id) getPositionDetail(route.query.id)
 }
-// getPositionDetail('1824359989330862081') // 测试使用
 
 // 取消
-const handleCancel = (hire) => { //  hire:是否是众聘岗位
+const handleCancel = () => {
   itemData.value = {}
-  router.push({ path: '/recruit/enterprise/position', query: { hire: hire ? 1 : 0 } })
+  router.push({ path: '/recruit/enterprise/position' })
   // 新增职位发布需更新账户信息
   if (route.query && !route.query?.id) {
     setTimeout(async () => {

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

@@ -1,42 +1,6 @@
 <template>
   <div>
     <CtForm ref="formPageRef" :items="items" style="width: 650px;">
-      <!-- <template #explain>
-        <div class="d-flex align-center font-size-13">
-          <div style="color: var(--v-error-base); cursor: pointer; text-decoration: underline;" @click="handleViewRule">
-            <v-icon size="20" color="error">mdi-help-circle-outline</v-icon>
-            众聘岗位规则说明;
-          </div>
-          <div class=" ml-5" style="color: var(--v-error-base);">
-            众聘岗位分配比例:推荐人占比{{ ratio.recommendRate }}%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 平台占比{{ ratio.headhuntRate }}%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 投递人占比{{ ratio.cvRate }}%
-          </div>
-        </div>
-      </template> -->
-      <template #numericalValue>
-        <div class="font-size-14 color-error my-1">
-          <div class="d-flex align-center font-size-13">
-          <div style="color: var(--v-error-base); cursor: pointer; text-decoration: underline;" @click="handleViewRule">
-            <v-icon size="20" color="error">mdi-help-circle-outline</v-icon>
-            众聘岗位规则说明;
-          </div>
-          <div class=" ml-5" style="color: var(--v-error-base);">
-            众聘岗位分配比例:推荐人占比{{ ratio.recommendRate }}%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 平台占比{{ ratio.headhuntRate }}%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 投递人占比{{ ratio.cvRate }}%
-          </div>
-        </div>
-          <div class="d-flex">
-            按众聘岗位分配比例计算后的赏金: 
-            <span class="calculation ml-3">推荐人{{ calculation('hirePrice', 1, true) }}元</span>
-            <span class="calculation">平台{{ calculation('hirePrice', 0, true) }}元</span>
-            <span class="calculation">投递人{{ calculation('hirePrice', 2, true) }}元</span>
-          </div>
-          <!-- <div class="d-flex">
-            按众聘岗位分配比例计算后的积分: 
-            <span class="calculation ml-3">推荐人{{ calculation('hirePoint', 1) }}点</span>
-            <span class="calculation">平台{{ calculation('hirePoint', 0) }}点</span>
-            <span class="calculation">投递人{{ calculation('hirePoint', 2) }}点</span>
-          </div> -->
-        </div>
-      </template>
       <template #positionId="{ item }">
         <v-menu :close-delay="1" :open-delay="0" v-bind="$attrs">
           <template v-slot:activator="{  props }">
@@ -52,10 +16,6 @@
         <v-btn class="ml-3 half-button" color="primary" style="margin-top: 2px;" @click="useJobTemplate(item)">岗位模板</v-btn>
       </template>
     </CtForm>
-
-    <CtDialog :visible="show" :widthType="1" titleClass="text-h6" title="众聘岗位规则说明" :footer="false" @close="show = false">
-      <RulePage />
-    </CtDialog>
   </div>
 </template>
 
@@ -65,103 +25,21 @@ import CtForm from '@/components/CtForm'
 import { reactive, ref, watch } from 'vue'
 import textUI from '@/components/FormUI/TextInput'
 import jobTypeCard from '@/components/jobTypeCard'
-import RulePage from './rule.vue'
-import { commissionCalculation } from '@/utils/position'
-import { getPublicRatio, getRecruitPositionDetails } from '@/api/recruit/enterprise/position'
+import { getRecruitPositionDetails } from '@/api/recruit/enterprise/position'
 
 const props = defineProps({
   itemData: Object
 })
 
-const show = ref(false)
-const ratio = ref({})
-
-// 按分配比例计算金额积分
-const calculation = (key, type) => {
-  const value = items.value.options.find(e => e.key === key).value
-  return commissionCalculation(value, type)
-}
-
 const getValue = (key) => {
   return items.value.options.find(e => e.key === key)
 }
 
-// 是否众聘岗位
-const handleChangePublic = (val) => {
-  items.value.options.forEach(e => {
-    if (!val && Object.prototype.hasOwnProperty.call(e, 'hide')) {
-      e.hide = true
-      e.value = null
-      return
-    }
-    if (val && Object.prototype.hasOwnProperty.call(e, 'show')) {
-      e.hide = false
-      return
-    }
-    // if (e.slotName === 'explain') e.hide = val ? false : true
-  })
-}
-
 const formPageRef = ref()
 let query = reactive({})
 
 const items = ref({
   options: [
-    {
-      type: 'ifRadio',
-      key: 'hire',
-      value: false,
-      label: '众聘岗位 *',
-      width: 90,
-      disabled: false,
-      hideDetails: true,
-      items: [
-        { label: '是', value: true },
-        { label: '否', value: false }
-      ],
-      change: val => handleChangePublic(val)
-    },
-    // {
-    //   slotName: 'explain',
-    //   noParam: true,
-    //   value: null,
-    //   disabled: false,
-    //   hide: true,
-    //   flexStyle: 'mb-3'
-    // },
-    {
-      type: 'number',
-      key: 'hirePrice',
-      value: null,
-      label: '请填写点数 (1赏金=10点数,点数填入不得少于10且为10的倍数)',
-      suffix: '点',
-      // integer: true,
-      hide: true,
-      show: true,
-      disabled: false,
-      hideDetails: true,
-      change: val => hirePriceChange(val, 'hirePrice')
-      // col: 6,
-      // flexStyle: 'mr-3'
-    },
-    // {
-    //   type: 'number',
-    //   key: 'hirePoint',
-    //   value: null,
-    //   label: '众聘奖励积分数',
-    //   suffix: '点',
-    //   hide: true,
-    //   col: 6,
-    //   disabled: false,
-    //   hideDetails: true,
-    //   show: false
-    // },
-    {
-      slotName: 'numericalValue',
-      noParam: true,
-      show: true,
-      hide: true
-    },
     {
       type: 'text',
       key: 'name',
@@ -179,15 +57,6 @@ const items = ref({
       readonly: true,
       rules: [v => !!v || '请选择职位类型']
     },
-    // {
-    //   type: 'datePicker',
-    //   key: 'expireTime',
-    //   dateType: 'date', // 时间类型 year month date time
-    //   value: null,
-    //   label: '到期时间 *',
-    //   outlined: true,
-    //   rules: [v => !!v || '请选择到期时间']
-    // },
     {
       type: 'wangEditor',
       key: 'content',
@@ -203,64 +72,16 @@ const items = ref({
       label: '岗位要求 *',
       maxLength: 5000,
       rules: '请填写岗位要求'
-    },
-    // {
-    //   type: 'textarea',
-    //   key: 'content',
-    //   rows: 10,
-    //   value: '',
-    //   label: '岗位职责 *',
-    //   counter: 5000,
-    //   clearable: true,
-    //   rules: [
-    //     value => {
-    //       if (value) return true
-    //       return '请输入岗位职责'
-    //     },
-    //     value => {
-    //       if (value?.length <= 5000) return true
-    //       return '请输入2-5000个字符'
-    //     }
-    //   ]
-    // },
-    // {
-    //   type: 'textarea',
-    //   key: 'requirement',
-    //   rows: 10,
-    //   value: '',
-    //   label: '岗位要求 *',
-    //   counter: 5000,
-    //   clearable: true,
-    //   rules: [
-    //     value => {
-    //       if (value) return true
-    //       return '请输入岗位要求'
-    //     },
-    //     value => {
-    //       if (value?.length <= 5000) return true
-    //       return '请输入2-5000个字符'
-    //     }
-    //   ]
-    // }
+    }
   ]
 })
 
-// items.value.options.forEach(e => e.rules = []) // 测试使用
-
-// 获取众聘分配比例
-const getRatio = async () => {
-  const data = await getPublicRatio()
-  ratio.value = data
-}
-getRatio()
-
 // 编辑回显
 watch(
   () => props.itemData,
   (val) => {
     if (!Object.keys(val).length) return
     items.value.options.forEach(e => {
-      if (Object.prototype.hasOwnProperty.call(e, 'disabled')) e.disabled = val.hire
       if (e.labelKey) {
         query[e.key] = val[e.key]
         e.value = val[e.labelKey]
@@ -268,7 +89,6 @@ watch(
       }
       if (e.noParam) return
       e.value = val[e.key]
-      e.change && e.change(e.value)
     })
   },
   { immediate: true },
@@ -313,37 +133,17 @@ const useJobTemplate = async () => {
   }
 }
 
-// 众聘规则查看
-const handleViewRule = () => {
-  show.value = true
-}
-
-// 众聘规则查看
-const hirePriceChange = (value, key) => {
-  let calcCost = value-0
-  if (calcCost < 10 ) calcCost = 10
-  else {
-    calcCost = parseInt(calcCost/10)*10
-  }
-  const obj = items.value.options.find(k => k.key === key)
-  if (obj) {
-    obj.value = calcCost
-  }
-}
-
 const getQuery = async () => {
   const { valid } = await formPageRef.value.formRef.validate()
   if (!valid) return
-  const obj = {}
+  const obj = {
+    hirePrice: 0,
+    hire: false
+  }
   items.value.options.forEach(e => {
     if (e.noParam) return
     else obj[e.key] = e.value
   })
-  // 不是众聘岗位的默认给0
-  if (!obj.hire) {
-    // obj.hirePoint = 0
-    obj.hirePrice = 0
-  }
   if (!obj.content) {
     Snackbar.warning('请填写岗位职责')
     return 'failed'

+ 0 - 200
src/views/recruit/enterprise/positionManagement/components/details.vue

@@ -1,200 +0,0 @@
-<template>
-  <div class="default-width mb-5">
-    <div class="banner px-6">
-      <div class="banner-title d-flex justify-space-between">
-        <div class="d-flex align-center">
-          <h1 class="ellipsis">{{ info.name }}</h1>
-          <span class="salary">{{ info.payFrom }}-{{ info.payTo }}/{{ info.payName }}</span>
-          <svg-icon v-if="info.hire" class="ml-5" name="pin" size="50"></svg-icon>
-        </div>
-        <span class="refresh-time">{{ timesTampChange(info.updateTime) }} {{ $t('common.refresh') }} <v-icon color="warning" size="20">mdi-alert-outline</v-icon></span>
-      </div>
-      <div class="banner-tags mt-4">
-        <span v-for="k in desc" :key="k.mdi" class="mr-10">
-          <v-icon color="var(--color-666)" size="20">{{ k.mdi }}</v-icon>
-          <span class="ml-1">{{ info[k.value] }}</span>
-        </span>
-      </div>
-      <div class="banner-tools my-4">
-        <v-chip size="small" label v-for="(k, i) in info.tagList" :key="i" class="mr-1" color="primary">{{ k }}</v-chip>
-      </div>
-      <div class="d-flex justify-space-between mb-5">
-        <div>
-          <v-chip v-if="info.hire && info.hirePrice && info.hirePrice > 0" label color="primary">赏金:{{ commissionCalculation(info.hirePrice, 1) }}元</v-chip>
-          <v-chip v-if="info.hire && info.hirePoint && info.hirePoint > 0" label color="primary" class="ml-1">积分:{{ commissionCalculation(info.hirePoint, 1) }}点</v-chip>
-        </div>
-        <div class="banner-tools-btns">
-          <v-btn class="button-item radius" color="primary" variant="outlined" @click="handleEdit">{{ $t('common.edit') }}</v-btn>
-        </div>
-      </div>
-      <v-divider></v-divider>
-      <div class="d-flex">
-          <div class="content-left">
-            <div v-if="Object.keys(info).length">
-              <div>{{ $t('position.jobResponsibilities') }}:</div>
-              <div v-if="info.content" class="requirement" v-html="info.content.replace(/\n/g, '</br>')"></div>
-              <div v-else>暂无</div>
-              <div class="mt-3">{{ $t('position.jobRequirements') }}:</div>
-              <div v-if="info.requirement" class="requirement" v-html="info.requirement.replace(/\n/g, '</br>')"></div>
-              <div v-else>暂无</div>
-            </div>
-            <v-divider class="my-3"></v-divider>
-            <div class="contact" v-if="Object.keys(info).length">
-              <div class="float-left d-flex align-center">
-                <v-img :src="info.contact.avatar || 'https://minio.citupro.com/dev/menduner/7.png'" :width="45" style="height: 45px;"></v-img>
-                <div class="ml-2">
-                  <div class="contact-name">{{ info.contact.name }}</div>
-                  <div class="contact-info">{{ info.enterprise.name }} · {{ info.contact.postNameCn }}</div>
-                </div>
-              </div>
-            </div>
-            <v-divider class="my-3"></v-divider>
-            <div>
-              <h4>{{ $t('position.address') }}</h4>
-              <div class="mt-2">
-                <v-icon size="25" color="primary">mdi-map-marker</v-icon>
-                <span style="color: var(--color-666);font-size: 15px;">{{ info.address }}</span>
-              </div>
-            </div>
-            <v-divider class="my-3"></v-divider>
-            <div v-if="enterprise && Object.keys(enterprise).length">
-              <h4>企业信息</h4>
-              <div class="mt-3">
-                <v-img class="float-left mr-5" :src="enterprise.logoUrl || 'https://minio.citupro.com/dev/menduner/company-avatar.png'" :width="60" height="60"></v-img>
-                <div>
-                  <div class="contact-name">{{ enterprise.name }}</div>
-                  <div class="contact-info">{{ enterprise.financingName }} | {{ enterprise.industryName }} | {{ enterprise.scaleName }}</div>
-                </div>
-              </div>
-              <div class="mt-5">
-                <v-chip v-for="(k, i) in enterprise.welfareList" :key="i" label color="primary" class="mr-2" size="small">{{ k }}</v-chip>
-              </div>
-            </div>
-          </div>
-      </div>
-    </div>
-  </div>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
-</template>
-
-<script setup>
-defineOptions({ name: 'position-details' })
-import { ref } from 'vue'
-import { useRouter } from 'vue-router'
-import { timesTampChange } from '@/utils/date'
-import { getJobDetails } from '@/api/position'
-import { dealDictObjData } from '@/utils/position'
-import { commissionCalculation } from '@/utils/position'
-
-const router = useRouter()
-const { id } = router.currentRoute.value.params
-
-// 职位详情
-const info = ref({})
-const enterprise = ref({})
-const getPositionDetail = async () => {
-  const data = await getJobDetails({ id })
-  if (!data || !Object.keys(data).length) return
-  enterprise.value = { ...dealDictObjData({}, data.enterprise), ...data.enterprise }
-  info.value = { ...data, ...dealDictObjData({}, data) }
-}
-getPositionDetail()
-
-const desc = [
-  { mdi: 'mdi-map-marker-outline', value: 'areaName' },
-  { mdi: 'mdi-school-outline', value: 'eduName' },
-  { mdi: 'mdi-clock-time-ten-outline', value: 'expName' },
-  { mdi: 'mdi-file-tree-outline', value: 'positionName' }
-]
-
-const handleEdit = () => {
-  if (!id) return
-  window.open(`/recruit/enterprise/position/edit?id=${id}`)
-}
-</script>
-
-<style lang="scss" scoped>
-.banner {
-  background-color: #fff;
-  padding: 18px 0 20px;
-}
-.banner-title {
-  line-height: 40px;
-  font-size: 28px;
-  font-weight: 600;
-}
-.banner-title h1 {
-  display: inline-block;
-  color: #37576c;
-  font-size: 28px;
-  margin-right: 30px;
-  margin-top: 1px;
-  max-width: 360px;
-  vertical-align: middle;
-}
-.button-item {
-  min-width: 110px;
-  height: 36px
-}
-.salary {
-  color: var(--v-error-base);
-  line-height: 41px;
-  font-weight: 600;
-  height: auto;
-  display: inline-block;
-  vertical-align: sub;
-}
-.refresh-time {
-  float: right;
-  color: var(--color-666);
-  font-size: 14px;
-  line-height: 66px;
-  vertical-align: sub;
-}
-.banner-tags span {
-  font-weight: 600;
-}
-.radius {
-  border-radius: 8px;
-}
-.content-left {
-  width: 100%;
-  padding: 20px 20px;
-}
-.content-right {
-  flex: 1;
-  padding: 20px 20px 20px 0;
-}
-.label-text {
-  color: #7f7a7a;
-  font-weight: 600;
-}
-.value-text {
-  color: #000;
-  font-weight: 400;
-}
-.requirement {
-  white-space: pre-wrap;
-  word-break: break-all;
-  line-height: 28px;
-  color: var(--color-333);
-  font-size: 15px;
-  text-align: justify;
-  letter-spacing: 0;
-}
-.contact {
-  height: 60px;
-  line-height: 60px;
-}
-.contact-name {
-  font-size: 20px;
-  font-weight: 500;
-  color: var(--color-222);
-  line-height: 28px;
-}
-.contact-info {
-  font-size: 15px;
-  color: var(--color-666);
-  line-height: 21px;
-  margin-top: 8px;
-}
-</style>

+ 6 - 62
src/views/recruit/enterprise/positionManagement/components/item.vue

@@ -1,7 +1,7 @@
 <template>
   <div>
     <div v-if="props.items.length" class="d-flex align-center mb-1">
-      <v-checkbox v-if="tab !== 3 && tab !== 4" v-model="selectAll" :label="!selectAll ? $t('common.selectAll') : `已选中${selectList.length}条`" hide-details color="primary" @update:model-value="handleChangeSelectAll"></v-checkbox>
+      <v-checkbox v-model="selectAll" :label="!selectAll ? $t('common.selectAll') : `已选中${selectList.length}条`" hide-details color="primary" @update:model-value="handleChangeSelectAll"></v-checkbox>
       <div v-if="tab === 1" class="ml-8">
         <v-btn :disabled="!selectAll" color="primary" variant="tonal" size="small" @click="handleAction(2, 'batch', {})">{{ $t('common.refresh') }}</v-btn>
         <v-btn class="mx-3" :disabled="!selectAll" color="primary" variant="tonal" size="small" @click="handleAction(3, 'top', {})">{{ $t('common.topping') }}</v-btn>
@@ -9,18 +9,17 @@
       </div>
       <v-btn v-if="tab === 2" class="ml-8" :disabled="!selectAll" color="primary" variant="tonal" size="small" @click="handleAction(1, 'activation', {})">{{ $t('common.activation') }}</v-btn>
     </div>
-    <div v-for="val in items" :key="val.id" class="itemBox mb-3" :style="{'height': val?.hire ? '162px' : '134px'}">
+    <div v-for="val in items" :key="val.id" class="itemBox mb-3" style="height: 134px;">
       <div class="d-flex justify-space-between" style="padding: 10px 20px;">
         <div class="position">
-          <div v-if="tab !== 3 && tab !== 4" class="item-select">
+          <div class="item-select">
             <v-checkbox v-model="val.select" hide-details color="primary" @update:model-value="handleChangeSelect"></v-checkbox>
           </div>
-          <div :class="[{'ml-10': tab !== 3 && tab !== 4} ,'d-flex' ,'align-center']">
+          <div :class="['ml-10' ,'d-flex' ,'align-center']">
             <span v-if="val.name.indexOf('style')" v-html="val.name" class="position-name"></span>
             <span v-else class="position-name">{{ val.name }}</span>
-            <svg-icon v-if="tab === 4" class="ml-3" name="pin" size="25"></svg-icon>
           </div>
-          <div :class="['mt-3', 'other-info', 'ellipsis', {'ml-10': tab !== 3 && tab !== 4}]">
+          <div :class="['mt-3', 'other-info', 'ellipsis', 'ml-10']">
             <span>{{ val.areaName }}</span>
             <span class="lines" v-if="val.areaName && val.eduName"></span>
             <span>{{ val.eduName }}</span>
@@ -31,57 +30,31 @@
             <span class="lines"></span>
             <span>{{ val.positionName }}</span>
           </div>
-          <div v-if="val?.hire" class="mt-2">
-            <v-chip v-if="val?.hirePrice && val.hirePrice > 0" class="mr-3" label color="primary" size="small">赏金:{{ commissionCalculation(val.hirePrice, 1) }}元</v-chip>
-            <!-- <v-chip v-if="val?.hirePoint && val.hirePoint > 0" label color="primary" size="small">积分:{{ commissionCalculation(val.hirePoint, 1) }}点</v-chip> -->
-          </div>
         </div>
         <div class="d-flex align-center">
           <v-btn v-if="tab === 1" class="ml-3" color="primary" @click="handleAction(2, '', val)">{{ $t('common.refresh') + $t('common.position') }}</v-btn>
           <v-btn v-if="tab === 2" color="primary" @click="handleAction(1, '', val)">{{ $t('common.activatePosition') }}</v-btn>
-          <v-chip v-if="tab === 4 && (val.status-0) === 99" color="warning" label>职位待发布,支付后成功后自动发布</v-chip>
-          <v-chip v-if="tab === 4 && val.status === '1'" color="error" class="cursor-pointer" label  style="text-decoration: underline;" @click="handleAction(1, '', val)">职位已关闭,点击激活职位</v-chip>
         </div>
       </div>
       <div class="bottom pa-5 d-flex justify-space-between align-center">
-        <!-- <div>{{ $t('position.refreshTime') }} :{{ timesTampChange(val.updateTime, 'Y-M-D') }} {{ val.expireDay && Number(val.expireDay) >= 1 ? `(${ val.expireDay }天后到期)` : '' }}</div> -->
         <div>{{ $t('position.refreshTime') }} :{{ timesTampChange(val.updateTime, 'Y-M-D') }}</div>
         <div class="d-flex">
           <div class="ml-10 d-flex">
-            <div v-if="tab === 4 && (val.status-0) === 99">
-              <span class="cursor-pointer color-primary" @click="toPay(val)">去支付</span>
-              <span class="lines"></span>
-            </div>
             <div v-if="tab === 1">
               <span class="cursor-pointer actions" @click="handleAction(3, '', val)">{{ $t('common.topping') }}</span>
               <span class="lines"></span>
               <span class="cursor-pointer actions" @click="handleAction(0, '', val)">{{ $t('common.close') }}</span>
               <span class="lines"></span>
             </div>
-            <div v-if="tab !== 3" class="cursor-pointer actions" @click="handleEdit(val)">{{ $t('common.edit') }}</div>
-            <div v-if="tab === 4 && (val.status - 0) !== 99 && val.status !== '1'">
-              <span class="lines"></span>
-              <span class="cursor-pointer actions" @click="handleAction(0, '', val)">{{ $t('common.close') }}</span>
-            </div>
+            <div class="cursor-pointer actions" @click="handleEdit(val)">{{ $t('common.edit') }}</div>
           </div>
         </div>
       </div>
     </div>
   </div>
-  <!-- returnUrl="/recruit/enterprise/position?hire=1" -->
-  <confirmPaymentDialog
-    v-if="showConfirmPaymentDialog"
-    :cost="cost"
-    :spuId="spuId"
-    :spuName="spuName"
-    :orderType="2"
-    @paySuccess="paySuccess"
-    @close="showConfirmPaymentDialog = false"
-  ></confirmPaymentDialog>
 </template>
 
 <script setup>
-import { commissionCalculation } from '@/utils/position'
 defineOptions({ name: 'enterprise-position-item'})
 import { ref, watch } from 'vue'
 import { useRouter } from 'vue-router'
@@ -89,7 +62,6 @@ import { timesTampChange } from '@/utils/date'
 import { useI18n } from '@/hooks/web/useI18n'
 import { closeJobAdvertised, enableJobAdvertised, refreshJobAdvertised, topJobAdvertised } from '@/api/position'
 import Snackbar from '@/plugins/snackbar'
-import confirmPaymentDialog from '@/components/pay/confirmPaymentDialog.vue'
 
 const { t } = useI18n()
 const emit = defineEmits(['refresh'])
@@ -101,25 +73,6 @@ const props = defineProps({
   items: Array
 })
 
-const showConfirmPaymentDialog = ref(false)
-const cost = ref(0)
-const spuId = ref('')
-const spuName = ref('')
-const toPay = (val) => {
-  spuId.value = val.id || ''
-  spuName.value = val.name || ''
-  cost.value = val.hirePrice
-  // 打开支付弹窗
-  showConfirmPaymentDialog.value = true
-}
-// 支付成功
-const paySuccess = async () => {
-  showConfirmPaymentDialog.value = false
-  setTimeout(() => {
-    emit('refresh')
-  }, 1000)
-}
-
 const selectAll = ref(false) // 全选
 const selectList = ref([]) // 选中列表
 const dealSelect = () => {
@@ -184,11 +137,6 @@ const router = useRouter()
 const handleEdit = (val) => {
   router.push(`/recruit/enterprise/position/edit?id=${val.id}`)
 }
-
-// 职位详情
-// const handleDetails = (val) => {
-//   window.open(`/recruit/enterprise/position/details/${val.id}`)
-// }
 </script>
 
 <style scoped lang="scss">
@@ -199,10 +147,6 @@ const handleEdit = (val) => {
 .position-name {
   color: var(--color-333);
   font-size: 19px;
-  cursor: pointer;
-  &:hover {
-    color: var(--v-primary-base);
-  }
 }
 .position {
   max-width: 46%;

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

@@ -36,7 +36,6 @@ defineOptions({ name: 'enterprise-position-list'})
 import { ref } from 'vue'
 import TextUI from '@/components/FormUI/TextInput'
 import PositionItem from './components/item.vue'
-import { useRoute } from 'vue-router'; const route = useRoute()
 import { useRouter } from 'vue-router'; const router = useRouter()
 import { getJobAdvertisedList, getJobAdvertisedExport } from '@/api/position'
 import { dealDictArrayData } from '@/utils/position'
@@ -57,15 +56,12 @@ const query = ref({
   hire: false // true 众聘岗位
 })
 
-const showHire = (route.query?.hire - 0) || 0
-const tab = ref(showHire ? 4: 1)
-// if (showHire) history.replaceState({ ...route.query, hire: 0 }, '', route.path) // 更新浏览器历史记录,不触发页面重新加载 ( 目的:去除目标参数 )
+const tab = ref(1)
 
 const tabList = [
   { label: t('position.recruitmentInProgress'), value: 1 },
   { label: t('position.closed'), value: 2 },
-  // { label: t('position.expiredPosition'), value: 3 },
-  { label: t('position.publicRecruitment'), value: 4 }
+  // { label: t('position.expiredPosition'), value: 3 }
 ]
 
 const items = ref([])
@@ -93,13 +89,9 @@ const handleExport = async () => {
 
 // 获取职位列表
 const getPositionList = async () => {
-  // query.value.hasExpiredData = tab.value === 4 ? true : false
-  query.value.hire = tab.value === 4 ? true : false
-
   if (tab.value !== 3) {
     query.value.status = tab.value === 1 ? 0 : (tab.value === 2 ? 1 : null)
   }
-
   const { list, total: number } = await getJobAdvertisedList(query.value)
   if (!list.length) {
     if (query.value.name) tipsText.value = '暂无数据,请更换关键词后再试'