Browse Source

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

lifanagju_citu 9 months ago
parent
commit
352fd59cb3

+ 16 - 0
src/api/recruit/enterprise/information/index.js

@@ -0,0 +1,16 @@
+import request from '@/config/axios'
+
+// 获取企业实名认证信息
+export const getEnterpriseAuth = async () => {
+  return await request.get({
+    url: '/app-admin-api/menduner/system/enterprise/get/auth'
+  })
+}
+
+// 实名认证
+export const saveEnterpriseAuth = async (data) => {
+  return await request.post({
+    url: '/app-admin-api/menduner/system/enterprise/auth/save',
+    data
+  })
+}

+ 4 - 0
src/components/Upload/img.vue

@@ -24,9 +24,13 @@ import { useI18n } from '@/hooks/web/useI18n'
 import Snackbar from '@/plugins/snackbar'
 
 const emit = defineEmits(['success', 'delete'])
+const props = defineProps({
+  value: String
+})
 
 const { t } = useI18n()
 const src = ref('')
+if (props.value) src.value = props.value
 
 // 选择文件
 const fileInput = ref()

+ 6 - 3
src/layout/enterprise.vue

@@ -16,10 +16,10 @@
         </div>
         <div class="box pa-3">
           <div v-if="!isInWhiteList(route.path, whiteList)" class="box-content">
-            <router-view></router-view>
+            <router-view :key="key"></router-view>
           </div>
           <div v-else class="full">
-            <router-view></router-view>
+            <router-view :key="key"></router-view>
           </div>
         </div>
       </div>
@@ -34,10 +34,13 @@ import Headers from './company/navBar.vue'
 import Slider from './company/slider.vue'
 import side from './company/side.vue'
 import { useRouter, useRoute } from 'vue-router'
-import { watch, ref } from 'vue'
+import { watch, ref, computed } from 'vue'
 
 const router = useRouter()
 const route = useRoute()
+const key = computed(() => {
+  return route.path + Math.random()
+})
 
 const whiteList = [
   '/recruit/enterprise/talentPool/details',

+ 51 - 0
src/utils/validate.js

@@ -0,0 +1,51 @@
+// 效验身份证号码
+export const isValidIdCard18 = (idCard) => {
+  // 身份证号码正则表达式(18位)
+  var regex = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(10|11|12))(([012]\d)|3[01])\d{3}(\d|X|x)$/
+
+  // 校验长度,类型
+  if (!regex.test(idCard)) {
+    return false
+  }
+
+  // 提取出生年月日,并验证是否为有效日期
+  var year = idCard.substring(6, 10)
+  var month = idCard.substring(10, 12)
+  var day = idCard.substring(12, 14)
+  var date = new Date(year, month - 1, day)
+
+  if (date.getFullYear() !== parseInt(year, 10) ||
+    date.getMonth() !== parseInt(month, 10) - 1 ||
+    date.getDate() !== parseInt(day, 10)) {
+    return false
+  }
+
+  // 校验码部分
+  var idCardBase = idCard.substring(0, 17)
+  var weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
+  var checkCodes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']
+  var sum = 0
+  for (var i = 0; i < idCardBase.length; i++) {
+    sum += idCardBase[i] * weights[i]
+  }
+  var index = sum % 11
+  var last = checkCodes[index]
+
+  return last.toUpperCase() === idCard.substring(17).toUpperCase()
+}
+
+// 身份证号码转换*号
+export const  maskNumber = (numberStr) => {
+  if (numberStr.length < 2) {
+    return numberStr
+  }
+  
+  // 提取首尾字符
+  const firstChar = numberStr[0]
+  const lastChar = numberStr[numberStr.length - 1]
+
+  const middleLength = numberStr.length - 2
+  const middleStars = '*'.repeat(middleLength)
+  
+  return firstChar + middleStars + lastChar
+}

+ 13 - 1
src/views/recruit/enterprise/elite/components/invite.vue

@@ -1,5 +1,5 @@
 <template>
-  <CtForm ref="CtFormRef" :items="formItems" style="height: 420px;">
+  <CtForm ref="CtFormRef" :items="formItems">
     <template #time="{ item }">
       <VueDatePicker 
         v-model="item.value"
@@ -25,6 +25,18 @@ const props = defineProps({
 const CtFormRef = ref()
 const formItems = ref({
   options: [
+    {
+      type: 'ifRadio',
+      key: 'type',
+      value: '0',
+      label: '发送方式 *',
+      width: 90,
+      noParam: true,
+      items: [
+        { label: '短信', value: '0' },
+        { label: '邮箱', value: '1' }
+      ]
+    },
     {
       slotName: 'time',
       key: 'time',

+ 7 - 7
src/views/recruit/enterprise/elite/components/table.vue

@@ -38,8 +38,8 @@
 
     <!-- 邀请面试 -->
     <CtDialog :visible="showInvite" :widthType="2" titleClass="text-h6" title="面试信息" @close="handleEditClose" @submit="handleEditSubmit">
-      <InvitePage v-if="showInvite && !inviteType" ref="inviteRef" :itemData="itemData"></InvitePage>
-      <PublicPage v-if="showInvite && inviteType" ref="publicRef" :item-data="itemData"></PublicPage>
+      <InvitePage v-if="showInvite" ref="inviteRef" :itemData="itemData"></InvitePage>
+      <!-- <PublicPage v-if="showInvite && inviteType" ref="publicRef" :item-data="itemData"></PublicPage> -->
     </CtDialog>
   </div>
 </template>
@@ -55,7 +55,7 @@ import { useI18n } from '@/hooks/web/useI18n'
 import { useUserStore } from '@/store/user'
 import Snackbar from '@/plugins/snackbar'
 import InvitePage from './invite.vue'
-import PublicPage from './public.vue'
+// import PublicPage from './public.vue'
 
 const { t } = useI18n()
 const emit = defineEmits(['refresh'])
@@ -154,21 +154,21 @@ const handlePreviewResume = async ({ url, id }) => {
 
 // 邀请面试
 const itemData = ref({})
-const inviteType = ref(false)
+// const inviteType = ref(false)
 const handleInterviewInvite = (item) => {
-  if (item?.job?.hire) inviteType.value = true
+  // if (item?.job?.hire) inviteType.value = true
   itemData.value = item
   showInvite.value = true
 }
 
 const handleEditClose = () => {
   showInvite.value = false
-  inviteType.value = false
+  // inviteType.value = false
   itemData.value = {}
 }
 
 const handleEditSubmit = async () => {
-  if (inviteType.value) return
+  // if (inviteType.value) return
   const { valid } = await inviteRef.value.CtFormRef.formRef.validate()
   if (!valid) return
   const query = inviteRef.value.getQuery()

+ 83 - 16
src/views/recruit/enterprise/informationManagement/informationSettingsComponents/authentication.vue

@@ -1,31 +1,48 @@
 <template>
   <div v-if="authentication" class="ml-3">
-    <div>
+    <div class="topTip" v-if="info.status === '0'">审核中,请耐心等待</div>
+    <div v-if="info.status === '1'">
       <v-icon color="primary">mdi-check-circle</v-icon>
       已通过实名认证
     </div>
+    <div class="topTip" v-if="info.status === '2'">
+      认证已被驳回,原因:{{ info.reason }}
+    </div>
     <div class="box mt-5">
-      <div>姓名:史迪奇</div>
-      <div class="mt-5">身份证号:4******************8</div>
+      <div>姓名:{{ info.name }}</div>
+      <div class="my-5">身份证号:{{ maskNumber(info.identityNo) }}</div>
+      <div class="d-flex" v-if="info.status !== '1'">
+        <span>国徽照</span>
+        <div class="ml-10" style="width: 120px; height: 120px;">
+          <v-img :src="info.backUrl" width="120" height="120" rounded alt=""/>
+        </div>
+      </div>
+      <div class="d-flex mt-5" v-if="info.status !== '1'">
+        <span>人像照</span>
+        <div class="ml-10" style="width: 120px; height: 120px;">
+          <v-img :src="info.frontUrl" width="120" height="120" rounded alt=""/>
+        </div>
+      </div>
     </div>
-    <v-btn color="primary" class="half-button mt-5" @click="authentication = !authentication">解绑</v-btn>
+    <v-btn v-if="info.status === '2'" class="buttons mt-5" color="primary" @click="handleAgain">重新认证</v-btn>
   </div>
+
   <div v-else>
-    <div class="topTip">为了您在平台有更好的操作体验,请进行实名认证</div>
-    <div class="d-flex align-center justify-center flex-column">
+    <div class="topTip" v-if="info.status !== '2'">为了您在平台有更好的操作体验,请进行实名认证</div>
+    <div class="d-flex align-center justify-center flex-column mt-5">
       <CtForm ref="CtFormRef" :items="formItems" style="width: 300px;">
-        <template #idCardImg1="{ item }">
+        <template #backUrl="{ item }">
           <div class="color-666 font-size-14 mr-5">{{ item.label }}</div>
-          <Img @success="val => item.value = val" @delete="item.value = ''"></Img>
+          <Img :value="item.value" @success="val => item.value = val" @delete="item.value = ''"></Img>
         </template>
-        <template #idCardImg2="{ item }">
+        <template #frontUrl="{ item }">
           <div class="mt-5 d-flex">
             <div class="color-666 font-size-14 mr-5">{{ item.label }}</div>
-            <Img @success="val => item.value = val" @delete="item.value = ''"></Img>
+            <Img :value="item.value" @success="val => item.value = val" @delete="item.value = ''"></Img>
           </div>
         </template>
       </CtForm>
-      <v-btn class="buttons mt-5" color="primary" @click="authentication = !authentication">{{ $t('common.submit') }}</v-btn>
+      <v-btn class="buttons mt-5" color="primary" @click="handleSave">{{ $t('common.submit') }}</v-btn>
     </div>
   </div>
 </template>
@@ -33,10 +50,15 @@
 <script setup>
 defineOptions({ name: 'authentication-page'})
 import { ref } from 'vue'
+import { isValidIdCard18, maskNumber } from '@/utils/validate'
+import { getEnterpriseAuth, saveEnterpriseAuth } from '@/api/recruit/enterprise/information'
+import Snackbar from '@/plugins/snackbar'
 
 // 是否已实名
-const authentication = ref(true)
+const info = ref({})
+const authentication = ref(false)
 const CtFormRef = ref()
+const query = ref({})
 
 const formItems = ref({
   options: [
@@ -49,19 +71,34 @@ const formItems = ref({
     },
     {
       type: 'text',
-      key: 'idCardNo',
+      key: 'identityNo',
       value: '',
       label: '身份证号码 *',
-      rules: [v => !!v || '请输入您的身份证号码']
+      rules: [
+        value => {
+          if (!value) {
+            return '请输入您的身份证号码'
+          }
+          return true
+        },
+        value => {
+          if (!isValidIdCard18(value)) {
+            return '请输入正确的身份证号码'
+          }
+          return true
+        }
+      ]
     },
     {
-      slotName: 'idCardImg1',
+      slotName: 'backUrl',
+      key: 'backUrl',
       value: '',
       label: '身份证-国徽照 *',
       rules: [v => !!v || '请上传']
     },
     {
-      slotName: 'idCardImg2',
+      key: 'frontUrl',
+      slotName: 'frontUrl',
       value: '',
       label: '身份证-人像照 *',
       rules: [v => !!v || '请上传']
@@ -69,6 +106,36 @@ const formItems = ref({
   ]
 })
 
+const getData = async () => {
+  const data = await getEnterpriseAuth()
+  if (!data) return authentication.value = false
+  authentication.value = true
+  info.value = data
+}
+getData()
+
+const handleAgain = () => {
+  formItems.value.options.forEach(item => {
+    item.value = info.value[item.key]
+  })
+  query.value.id = info.value.id
+  authentication.value = false
+}
+
+// 提交实名认证
+const handleSave = async () => {
+  const { valid } = await CtFormRef.value.formRef.validate()
+  if (!valid) return
+  formItems.value.options.forEach(item => {
+    query.value[item.key] = item.value
+  })
+  if (!query.value.backUrl || !query.value.frontUrl) return Snackbar.warning('请上传您的身份证照片')
+
+  await saveEnterpriseAuth(query.value)
+  Snackbar.success('提交成功')
+  query.value = {}
+  getData()
+}
 </script>
 
 <style scoped lang="scss">

+ 24 - 3
src/views/recruit/enterprise/informationManagement/informationSettingsComponents/basicInfo.vue

@@ -5,8 +5,8 @@
     <CtForm ref="CtFormRef" :items="formItems" style="width: 900px;margin: 0 auto">
       <template #name="{ item }">
         <div v-show="!item.show" class="text-right" style="width: 80px; line-height: 40px;">
-          <v-icon color="primary" size="20">mdi-shield-check</v-icon> <!-- mdi-shield-remove -->
-          <span style="color: var(--v-primary-base);font-size: 14px;">已认证</span>
+          <v-icon :color="statusInfo.color" size="20">{{ authInfo && authInfo.status === '1' ? 'mdi-shield-check' : 'mdi-shield-remove' }}</v-icon> <!-- mdi-shield-remove -->
+          <span style="color: var(--v-primary-base);font-size: 14px;">{{ statusInfo.label }}</span>
         </div>
       </template>
       <template #industryId="{ item }">
@@ -33,8 +33,9 @@
 
 <script setup>
 defineOptions({name: 'informationSettingsComponents-basicInfo'})
-import { ref, reactive } from 'vue'
+import { ref, reactive, computed } from 'vue'
 import { getEnterpriseBaseInfo, updateEnterpriseBaseInfo } from '@/api/enterprise'
+import { getEnterpriseAuth } from '@/api/recruit/enterprise/information'
 import { getDict } from '@/hooks/web/useDictionaries'
 import { useI18n } from '@/hooks/web/useI18n'
 import industryTypeCard from '@/components/industryTypeCard'
@@ -181,6 +182,26 @@ getDict('menduner_industry_type', {}, 'industryList').then(({ data }) => {
   industryList.value = data
 })
 
+// 获取企业实名信息
+const authInfo = ref({})
+const statusList = [
+  { label: '未认证', color: 'warning', value: null },
+  { label: '审核中', color: 'warning', value: '0' },
+  { label: '已认证', color: 'primary', value: '1' },
+  { label: '已驳回', color: 'error', value: '2' }
+]
+const getAuthInfo = async () => {
+  const data = await getEnterpriseAuth()
+  if (!data) return
+  authInfo.value = data
+}
+getAuthInfo()
+
+const statusInfo = computed(() => {
+  const obj = (authInfo.value && Object.keys(authInfo.value).length) ? statusList.find(e => e.value === authInfo.value.status) : statusList[0]
+  return obj
+})
+
 // 获取基本信息
 const getBaseInfo = async () => {
   const data = await getEnterpriseBaseInfo()

+ 44 - 41
src/views/recruit/enterprise/statistics/components/data.js

@@ -48,6 +48,7 @@ export const list = [
       },
       xAxis: {
         type: 'category',
+        name: '范围',
         data: ['18-22岁', '22-30岁', '30-39岁', '40-49岁', '50-59岁']
       },
       yAxis: {
@@ -55,9 +56,9 @@ export const list = [
       },
       grid: {
         left: '0',
-        top: '50',
-        right: '0',
-        bottom: 0,
+        top: '60',
+        right: '50',
+        bottom: '10',
         containLabel: true
       },
       series: [
@@ -80,6 +81,7 @@ export const list = [
       },
       xAxis: {
         type: 'category',
+        name: '范围',
         data: ['应届毕业生', '1年以上', '2年以上', '3年以上', '5年以上', '8年以上', '10年以上']
       },
       yAxis: {
@@ -87,9 +89,9 @@ export const list = [
       },
       grid: {
         left: '0',
-        top: '50',
-        right: '0',
-        bottom: 0,
+        top: '60',
+        right: '50',
+        bottom: '10',
         containLabel: true
       },
       series: [
@@ -112,6 +114,7 @@ export const list = [
       },
       xAxis: {
         type: 'category',
+        name: '范围',
         data: ['本科以上', '大专', '中专', '中技', '高中', '初中']
       },
       yAxis: {
@@ -119,9 +122,9 @@ export const list = [
       },
       grid: {
         left: '0',
-        top: '50',
-        right: '0',
-        bottom: 0,
+        top: '60',
+        right: '50',
+        bottom: '10',
         containLabel: true
       },
       series: [
@@ -136,36 +139,36 @@ export const list = [
       ]
     }
   },
-  {
-    col: 12,
-    option: {
-      title: {
-        text: '期望月薪'
-      },
-      xAxis: {
-        type: 'category',
-        data: ['3-5k', '5-8k', '8-12k', '12-15k', '15-20k', '20-30k', '面议']
-      },
-      yAxis: {
-        type: 'value'
-      },
-      grid: {
-        left: '0',
-        top: '50',
-        right: '0',
-        bottom: 0,
-        containLabel: true
-      },
-      series: [
-        {
-          data: [120, 200, 150, 80, 70, 110, 130],
-          type: 'bar',
-          barWidth: 40,
-          label: {
-            show: true
-          }
-        }
-      ]
-    }
-  }
+  // {
+  //   col: 12,
+  //   option: {
+  //     title: {
+  //       text: '期望月薪'
+  //     },
+  //     xAxis: {
+  //       type: 'category',
+  //       data: ['3-5k', '5-8k', '8-12k', '12-15k', '15-20k', '20-30k', '面议']
+  //     },
+  //     yAxis: {
+  //       type: 'value'
+  //     },
+  //     grid: {
+  //       left: '0',
+  //       top: '50',
+  //       right: '0',
+  //       bottom: 0,
+  //       containLabel: true
+  //     },
+  //     series: [
+  //       {
+  //         data: [120, 200, 150, 80, 70, 110, 130],
+  //         type: 'bar',
+  //         barWidth: 40,
+  //         label: {
+  //           show: true
+  //         }
+  //       }
+  //     ]
+  //   }
+  // }
 ]

+ 63 - 3
src/views/recruit/enterprise/statistics/components/resume.vue

@@ -1,22 +1,82 @@
 <template>
-  <v-container>
+  <!-- <v-container>
     <v-row>
       <v-col class="bgc" v-for="(val, i) in list" :key="i" :md="val.col">
         <Echarts :height="400" :option="val.option"></Echarts>
       </v-col>
     </v-row>
-  </v-container>
+  </v-container> -->
+  <div class="chart-box">
+    <div class="chart-item" v-for="(val, i) in list" :key="i">
+      <Echarts :height="400" :option="val.option"></Echarts>
+    </div>
+    <div class="fullChart">
+      <Echarts :height="400" :option="option"></Echarts>
+    </div>
+  </div>
 </template>
 
 <script setup>
 defineOptions({ name: 'resume-analysis'})
 import { list } from './data.js'
 
+const option = {
+  title: {
+    text: '期望月薪'
+  },
+  xAxis: {
+    type: 'category',
+    name: '范围',
+    data: ['3-5k', '5-8k', '8-12k', '12-15k', '15-20k', '20-30k', '面议']
+  },
+  yAxis: {
+    type: 'value'
+  },
+  grid: {
+    left: '0',
+    top: '60',
+    right: '50',
+    bottom: '10',
+    containLabel: true
+  },
+  series: [
+    {
+      data: [120, 200, 150, 80, 70, 110, 130],
+      type: 'bar',
+      barWidth: 40,
+      label: {
+        show: true
+      }
+    }
+  ]
+}
+
 </script>
 
 <style scoped lang="scss">
-.bgc {
+.chart-box {
+  width: 100%;
+  display: flex;
+  flex-wrap: wrap;
+  .chart-item {
+    width: calc((100% - 24px) / 2);
+    min-width: calc((100% - 24px) / 2);
+    max-width: calc((100% - 24px) / 2);
+    overflow: hidden;
+    transition: all .2s linear;
+    background-color: #f7f8fa;
+    border-radius: 8px;
+    margin: 0 12px 12px 0;
+    padding: 12px;
+    &:nth-child(2n) {
+      margin-right: 0;
+    }
+  }
+}
+.fullChart {
+  width: 100%;
   background-color: #f7f8fa;
   border-radius: 8px;
+  padding: 12px;
 }
 </style>

+ 0 - 11
src/views/recruit/enterprise/statistics/index.vue

@@ -1,11 +0,0 @@
-<template>
-  <div>统计分析</div>
-</template>
-
-<script setup>
-defineOptions({ name: 'enterprise-statistics'})
-</script>
-
-<style scoped lang="scss">
-
-</style>

+ 2 - 2
src/views/recruit/enterprise/statistics/overallAnalysis.vue

@@ -21,10 +21,10 @@
       <Overview class="mt-5"></Overview>
     </div>
     <div class="my-10">
-      <v-tabs v-model="tab" align-tabs="start" color="primary" bg-color="#f7f8fa">
+      <v-tabs class="mb-5" v-model="tab" align-tabs="start" color="primary" bg-color="#f7f8fa">
         <v-tab :value="1">应聘简历分析</v-tab>
       </v-tabs>
-      <ResumeAnalysis class="mt-5"></ResumeAnalysis>
+      <ResumeAnalysis></ResumeAnalysis>
     </div>
     <div>
       <v-tabs v-model="tab" align-tabs="start" color="primary" bg-color="#f7f8fa">