فهرست منبع

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

zhengnaiwen_citu 10 ماه پیش
والد
کامیت
ab797a12d0
30فایلهای تغییر یافته به همراه956 افزوده شده و 305 حذف شده
  1. 23 0
      src/api/recruit/enterprise/interview/index.js
  2. 2 2
      src/api/recruit/enterprise/personnel/index.js
  3. 2 2
      src/api/recruit/public/delivery/index.js
  4. 7 1
      src/components/CtDialog/index.vue
  5. 2 1
      src/locales/en.js
  6. 2 1
      src/locales/zh-CN.js
  7. 1 1
      src/store/loginType.js
  8. 2 3
      src/store/user.js
  9. 15 8
      src/utils/date.js
  10. 1 1
      src/views/recruit/enterprise/informationManagement/informationSettingsComponents/enterpriseAlbum.vue
  11. 1 1
      src/views/recruit/enterprise/interview/components/invite.vue
  12. 242 0
      src/views/recruit/enterprise/interview/components/item.vue
  13. 8 164
      src/views/recruit/enterprise/interview/index.vue
  14. 102 0
      src/views/recruit/enterprise/personnelManagement/components/invite.vue
  15. 45 24
      src/views/recruit/enterprise/personnelManagement/components/table.vue
  16. 10 4
      src/views/recruit/enterprise/personnelManagement/index.vue
  17. 57 24
      src/views/recruit/enterprise/publicRecruitmentManagement/deliver/components/table.vue
  18. 27 16
      src/views/recruit/enterprise/publicRecruitmentManagement/deliver/index.vue
  19. 8 8
      src/views/recruit/enterprise/statistics/components/overview.vue
  20. 1 1
      src/views/recruit/enterprise/statistics/overallAnalysis.vue
  21. 1 1
      src/views/recruit/enterprise/systemManagement/groupAccount/inviteConfirm.vue
  22. 47 6
      src/views/recruit/personal/PersonalCenter/components/interview/index.vue
  23. 184 0
      src/views/recruit/personal/PersonalCenter/components/interview/item.vue
  24. 88 5
      src/views/recruit/personal/PersonalCenter/components/interviewSchedule.vue
  25. 16 2
      src/views/recruit/personal/PersonalCenter/dynamic/left.vue
  26. 27 5
      src/views/recruit/personal/PersonalCenter/dynamic/right.vue
  27. 10 0
      src/views/recruit/personal/home/index.vue
  28. 6 20
      src/views/recruit/personal/remuse/components/basicInfo.vue
  29. 2 1
      src/views/recruit/personal/remuse/components/selfEvaluation.vue
  30. 17 3
      src/views/recruit/personal/shareJob/sendResume/simple.vue

+ 23 - 0
src/api/recruit/enterprise/interview/index.js

@@ -22,4 +22,27 @@ export const cancelInterviewInvite = async (data) => {
     url: '/app-admin-api/menduner/system/interview-invite/cancellation',
     data
   })
+}
+
+// 完成面试
+export const completedInterviewInvite = async (id) => {
+  return await request.post({
+    url: `/app-admin-api/menduner/system/interview-invite/completed?id=${id}`
+  })
+}
+
+// 未能爽约面试
+export const noAttendInterviewInvite = async (data) => {
+  return await request.post({
+    url: '/app-admin-api/menduner/system/interview-invite/not/attended',
+    data
+  })
+}
+
+// 面试反馈
+export const feedbackInterviewInvite = async (data) => {
+  return await request.post({
+    url: '/app-admin-api/menduner/system/interview-invite/feedback',
+    data
+  })
 }

+ 2 - 2
src/api/recruit/enterprise/personnel/index.js

@@ -16,9 +16,9 @@ export const joinEliminate = async (data) => {
 }
 
 // 招聘端-牛人管理-入职
-export const personEntryByEnterprise = async (ids) => {
+export const personEntryByEnterprise = async (id) => {
   return await request.post({
-    url: `/app-admin-api/menduner/system/person-cv/entry?ids=${ids}`
+    url: `/app-admin-api/menduner/system/person-cv/entry?id=${id}`
   })
 }
 

+ 2 - 2
src/api/recruit/public/delivery/index.js

@@ -23,9 +23,9 @@ export const hireJobCvRelSettlement = async (id) => {
 }
 
 // 入职
-export const hireJobCvRelEntry = async (ids) => {
+export const hireJobCvRelEntry = async (id) => {
   return await request.post({
-    url: `/app-admin-api/menduner/system/person-cv/entry?ids=${ids}`
+    url: `/app-admin-api/menduner/system/person-cv/entry?id=${id}`
   })
 }
 

+ 7 - 1
src/components/CtDialog/index.vue

@@ -15,6 +15,7 @@
         <span class="d-flex align-center justify-space-between" :class="[props.titleClass]">
           {{ props.title }}
           <v-btn
+            v-if="props.closeable"
             icon
             elevation="0"
             @click="handleClose"
@@ -34,6 +35,7 @@
         <v-card-actions>
           <v-spacer></v-spacer>
           <v-btn
+            v-if="props.closeable"
             color="primary"
             text
             @click="handleClose"
@@ -97,7 +99,11 @@ const props = defineProps({
   flexBox: {
     type: Boolean,
     default: false
-  }
+  },
+  closeable: {
+    type: Boolean,
+    default: true
+  },
 })
 
 const show = ref(false)

+ 2 - 1
src/locales/en.js

@@ -50,7 +50,8 @@ export default {
     actions: 'operation',
     selectAll: 'Select All',
     copy: 'Copy',
-    noData: 'There is currently no data available'
+    noData: 'There is currently no data available',
+    more: 'More',
   },
   sys: {
     api: {

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

@@ -50,7 +50,8 @@ export default {
     actions: '操作',
     selectAll: '全选',
     copy: '复制',
-    noData: '暂无数据'
+    noData: '暂无数据',
+    more: '查看更多',
   },
   sys: {
     api: {

+ 1 - 1
src/store/loginType.js

@@ -3,7 +3,7 @@ import { defineStore } from 'pinia'
 export const useLoginType = defineStore('changeLoginType', {
   state: () => ({
     // loginType: 0
-    loginType: localStorage.getItem('loginType') || 0
+    loginType: localStorage.getItem('loginType') || 'personal'
   }),
   actions: {
     change(type) {

+ 2 - 3
src/store/user.js

@@ -26,7 +26,7 @@ export const useUserStore = defineStore('user',
   {
     state: () => ({
       // loginType: null, // 登录类型 // 330为企业登录
-      accountInfo: {}, // 登录返回的信息
+      accountInfo: localStorage.getItem('accountInfo') ? JSON.parse(localStorage.getItem('accountInfo')) : {}, // 登录返回的信息
       userInfo: localStorage.getItem('userInfo') ? JSON.parse(localStorage.getItem('userInfo')) : {}, // 当前登录账号信息
       baseInfo: localStorage.getItem('baseInfo') ? JSON.parse(localStorage.getItem('baseInfo')) : {}, // 人才信息
       userAccount: {}, // 用户账户信息
@@ -91,11 +91,10 @@ export const useUserStore = defineStore('user',
           const api = this.loginType ? null : getBaseInfo
           if (!api) return
           const data = await api({ userId: userId || this.accountInfo.userId })
-          if (!data) return localStorage.setItem('baseInfo', JSON.stringify('{}'))
+          if (!data) return localStorage.setItem('baseInfo', '{}')
           this.baseInfo = await this.getFieldText(data)
           localStorage.setItem('baseInfo', JSON.stringify(this.baseInfo))
         } catch (error) {
-          console.log(error, 'error')
           Snackbar.error(error)
         }
       },

+ 15 - 8
src/utils/date.js

@@ -1,19 +1,26 @@
 // 时间戳转换为年月日时分秒
-export const timesTampChange = (timestamp) => {
+export const timesTampChange = (timestamp, format = 'Y-M-D h:m:s') => {
+  if (!timestamp) return ''
   const date = new Date(timestamp)
-  const Y = date.getFullYear() + '-'
-  const M = (date.getMonth() + 1).toString().padStart(2, '0') + '-'
-  const D = date.getDate().toString().padStart(2, '0') + ' '
+  const Y = date.getFullYear().toString()
+  const M = (date.getMonth() + 1).toString().padStart(2, '0')
+  const D = date.getDate().toString().padStart(2, '0')
 
-  const h = date.getHours().toString().padStart(2, '0') + ':'
-  const m = date.getMinutes().toString().padStart(2, '0') + ':'
+  const h = date.getHours().toString().padStart(2, '0')
+  const m = date.getMinutes().toString().padStart(2, '0')
   const s = date.getSeconds().toString().padStart(2, '0')
-  const strDate = Y + M + D + h + m + s
-  return strDate
+
+  const formatter = { 'Y': Y, 'M': M, 'D': D, 'h': h, 'm': m, 's': s } // 替换format中的占位符
+  let formattedDate = format.replace(/Y|M|D|h|m|s/g, matched => formatter[matched]) // 使用正则表达式匹配并替换占位符
+  
+  if (!formattedDate) formattedDate = Y + '-' + M + '-' + D + ' ' + h + ':' + m + ':' + s
+
+  return formattedDate
 }
 
 // 将 Wed May 01 2024 00:00:00 GMT+0800 (中国标准时间) 转换为时间戳
 export const getTimeStamp = (str) => {
+  if (!str) return ''
   const date = new Date(str)
   return date.getTime()
 }

+ 1 - 1
src/views/recruit/enterprise/informationManagement/informationSettingsComponents/enterpriseAlbum.vue

@@ -53,7 +53,7 @@ const imgList = ref([])
 
 const getInfo = async () => {
   const data = await getEnterpriseBaseInfo()
-  if (!data) return
+  if (!data || !data.albumList) return
   imgList.value = data.albumList
 }
 getInfo()

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

@@ -12,7 +12,7 @@
 </template>
 
 <script setup>
-defineOptions({ name: 'formPage'})
+defineOptions({ name: 'interview-invite-form'})
 import { ref } from 'vue'
 
 const props = defineProps({

+ 242 - 0
src/views/recruit/enterprise/interview/components/item.vue

@@ -0,0 +1,242 @@
+<template>
+  <div class="listItem d-flex align-center pa-3 mb-3" v-for="(item, index) in items" :key="'item_' + index">
+    <div class="d-flex align-center">
+      <div class="mr-5 font-size-16" style="color: orange; width: 96px;">{{ timesTampChange(item.time) }}</div>
+      <v-avatar class="mr-2" size=40 :image="item?.person?.avatar || 'https://minio.citupro.com/dev/menduner/7.png'"></v-avatar>
+      <div class="d-flex flex-column mr-3" style="width: 110px;">
+        <span class="ellipsis mb-1">{{ item?.person?.name }}</span>
+        <span class="ellipsis" style="color: var(--color-999);">{{ item?.job?.name }}</span>
+      </div>
+    </div>
+    <div class="d-flex align-center right-item">
+      <div style="min-width: 80px;text-align: center;">
+        <v-icon v-if="item?.phone" class="mx-1" size="20" color="primary">mdi-phone-outline</v-icon>
+        <span>{{ item?.phone || '-' }}</span>
+      </div>
+      <div>
+        <!-- 面试类型: 线下面试 -->
+        <span v-if="item.type === '1'">
+          <v-icon class="mx-3" size="20" color="primary">mdi-account-multiple-outline</v-icon>
+          <span>{{ $t('interview.offlineInterview') }}</span>
+        </span>
+        <!-- 面试类型: 线上面试 -->
+        <span v-else class="d-flex">
+          <v-icon class="mx-3 mt-2" size="20" color="primary">mdi mdi-video-account</v-icon>
+          <span class="d-flex flex-column">
+            <span>{{ $t('interview.onlineInterview') }}</span>
+            <span style="color: var(--color-999);">腾讯会议</span>
+          </span>
+        </span>
+      </div>
+      <!-- 面试状态: '待接受'/'已取消' -->
+      <div :style="{ 'color': item.status !== '98' ? 'orange' :'var(--color-999)'}">
+        <v-icon size="30">mdi mdi-circle-small</v-icon>
+        <span>{{ statusList.find(e => e.value === item.status)?.label }}</span>
+      </div>
+      <div>
+        <span v-if="editStatus.indexOf(item.status) !== -1" class="font-size-15 color-primary" @click="handleActionClick('edit', item)">修改面试</span>
+        <span v-if="againStatus.indexOf(item.status) !== -1" class="font-size-15 color-primary" @click="handleActionClick('edit', item)">重新邀约</span>
+        <v-menu v-if="actionItems(item.status).length">
+          <template v-slot:activator="{ props }">
+            <v-icon v-bind="props" class="mx-3" size="20" color="primary">mdi-dots-horizontal</v-icon>
+          </template>
+          <v-list>
+            <v-list-item
+              v-for="(k, index) in actionItems(item.status)"
+              :key="index"
+              :value="index"
+              color="primary"
+              @click="handleActionClick(k.value, item)"
+            >
+              <v-list-item-title>{{ k.title }}</v-list-item-title>
+            </v-list-item>
+          </v-list>
+        </v-menu>
+      </div>
+    </div>
+  </div>
+
+  <!-- 修改面试、重新邀约 -->
+  <CtDialog :visible="showInvite" :widthType="2" titleClass="text-h6" title="面试信息" @close="handleEditClose" @submit="handleEditSubmit">
+    <InvitePage v-if="showInvite" ref="inviteRef" :itemData="itemData" :position="positionItems"></InvitePage>
+  </CtDialog>
+
+  <!-- 取消面试 -->
+  <CtDialog :visible="cancelInvite" :widthType="2" titleClass="text-h6" title="取消面试" @close="handleCancelClose" @submit="handleCancelSubmit">
+    <TextArea v-model="cancelQuery.reason" :item="textItems"></TextArea>
+  </CtDialog>
+
+  <!-- 爽约、填写反馈 -->
+  <CtDialog :visible="show" :widthType="2" titleClass="text-h6" :title="currentAction === 'feedback' ? '填写反馈' : '填写爽约原因'" @close="handleClose" @submit="handleSubmit">
+    <TextArea v-if="currentAction === 'feedback'" v-model="query.evaluate" :item="textItems2"></TextArea>
+    <TextArea v-else v-model="query.reason" :item="textItems2"></TextArea>
+  </CtDialog>
+
+</template>
+
+<script setup>
+defineOptions({ name: 'interview-item'})
+import { ref } from 'vue'
+import { timesTampChange } from '@/utils/date'
+import { useI18n } from '@/hooks/web/useI18n'
+import { completedInterviewInvite, cancelInterviewInvite, saveInterviewInvite, noAttendInterviewInvite, feedbackInterviewInvite } from '@/api/recruit/enterprise/interview'
+import InvitePage from './invite.vue'
+import Snackbar from '@/plugins/snackbar'
+import Confirm from '@/plugins/confirm'
+
+defineProps({
+  items: Array,
+  statusList: Array,
+  positionItems: Array
+})
+const emit = defineEmits(['refresh', 'action'])
+
+const { t } = useI18n()
+const editStatus = ['1', '0'] // 修改面试状态
+const againStatus = ['98', '99'] // 重新邀约状态
+const actions = ref([
+  { title: '完成面试', value: 'completed' },
+  { title: '取消面试', value: 'cancel' },
+  { title: '填写反馈', value: 'feedback' },
+  { title: '爽约', value: 'attended' }
+])
+// 邀请
+const itemData = ref({})
+const showInvite = ref(false)
+const inviteRef = ref()
+// 取消
+const cancelInvite = ref(false)
+const cancelQuery = ref({
+  id: null,
+  reason: null
+})
+const textItems = ref({
+  label: '取消原因 *',
+  clearable: true
+})
+
+// 爽约、反馈
+const currentAction = ref('feedback')
+const show = ref(false)
+const query = ref({})
+const textItems2 = ref({
+  label: '反馈 *',
+  clearable: true
+})
+
+const obj = {
+  '0': [1],
+  '1': [1],
+  '2': [0],
+  '3': [2]
+}
+const actionItems = (status) => {
+  const type = obj[status]
+  if (!type || !type.length) return []
+  const data = type.map(e => actions.value[e])
+  return data
+}
+
+// 完成面试
+const handleFinish = (item) => {
+  if (!item.id) return
+  Confirm(t('common.confirmTitle'), '是否确认已完成面试?').then(async () => {
+    await completedInterviewInvite(item.id)
+    Snackbar.success(t('common.operationSuccessful'))
+    emit('refresh')
+  })
+}
+
+// 操作按钮
+const handleActionClick = (value, item) => {
+  // 修改、重新邀约
+  if (value === 'edit') {
+    itemData.value = item
+    showInvite.value = true
+  }
+  // 取消
+  if (value === 'cancel') {
+    cancelQuery.value.id = item.id
+    cancelInvite.value = true
+  }
+  // 完成
+  if (value === 'completed') handleFinish(item)
+  // 爽约、反馈
+  if (value === 'feedback' || value === 'attended') {
+    currentAction.value = value
+    textItems2.value.label = value === 'feedback' ? '反馈 *' : '爽约原因 *'
+    query.value = value === 'feedback' ? { id: item.id, evaluate: null } : { id: item.id, reason: null }
+    show.value = true
+  }
+}
+
+// 修改面试、重新邀约
+const handleEditClose = () => {
+  itemData.value = {}
+  showInvite.value = false
+}
+
+const handleEditSubmit = async () => {
+  const query = inviteRef.value.getQuery()
+  if (!Object.keys(query).length) return
+  await saveInterviewInvite(query)
+  Snackbar.success(t('common.operationSuccessful'))
+  handleEditClose()
+  emit('refresh')
+}
+
+// 取消面试
+const handleCancelClose = () => {
+  cancelInvite.value = false
+  cancelQuery.value = {
+    id: null,
+    reason: null
+  }
+}
+
+const handleCancelSubmit = async () => {
+  if (!cancelQuery.value.reason) return Snackbar.warning('请填写取消原因')
+  await cancelInterviewInvite(cancelQuery.value)
+  Snackbar.success(t('common.operationSuccessful'))
+  handleCancelClose()
+  emit('refresh')
+}
+
+// 爽约、反馈
+const handleClose = () => {
+  show.value = false
+  query.value = {}
+}
+
+const handleSubmit = async () => {
+  const key = currentAction.value === 'feedback' ? 'evaluate' : 'reason'
+  if (!query.value[key]) return Snackbar.warning('请填写您的' + (currentAction.value === 'feedback' ? '反馈' : '爽约原因'))
+  const api = currentAction.value === 'feedback' ? feedbackInterviewInvite : noAttendInterviewInvite
+  await api(query.value)
+  Snackbar.success(t('common.operationSuccessful'))
+  emit('refresh')
+  handleClose()
+}
+</script>
+
+<style scoped lang="scss">
+.listItem {
+  cursor: pointer;
+  width: 100%;
+  min-width: 600px;
+  overflow: auto;
+  height: 76px;
+  border: 1px solid #e5e6eb;
+  border-radius: 5px;
+  
+  &:hover {
+    background-color: var(--color-f8);
+  }
+  .right-item {
+    width: 100%;
+    div {
+      width: 25%;
+    }
+  }
+}
+</style>

+ 8 - 164
src/views/recruit/enterprise/interview/index.vue

@@ -58,65 +58,7 @@
       <v-divider style="height: auto;" class="mr-5" vertical></v-divider>
       <div style="flex: 1;overflow: hidden;">
         <div v-if="items.length">
-          <div
-            class="listItem d-flex align-center pa-3 mb-3"
-            v-for="(item, index) in items" :key="'item_' + index"
-          >
-            <div class="d-flex align-center">
-              <span class="mr-5 font-size-16" style="color: orange;">{{ timesTampChange(item.time) }}</span>
-              <v-avatar class="mr-2" size=40 :image="item?.person?.avatar || 'https://minio.citupro.com/dev/menduner/7.png'"></v-avatar>
-              <div class="d-flex flex-column mr-3" style="width: 110px;">
-                <span class="ellipsis mb-1">{{ item?.person?.name }}</span>
-                <span class="ellipsis" style="color: var(--color-999);">{{ item?.job?.name }}</span>
-              </div>
-            </div>
-            <div class="d-flex align-center right-item">
-              <div style="min-width: 80px;text-align: center;">
-                <v-icon v-if="item?.phone" class="mx-1" size="20" color="primary">mdi-phone-outline</v-icon>
-                <span>{{ item?.phone || '-' }}</span>
-              </div>
-              <div>
-                <!-- 面试类型: 线下面试 -->
-                <span v-if="item.type === '1'">
-                  <v-icon class="mx-3" size="20" color="primary">mdi-account-multiple-outline</v-icon>
-                  <span>{{ $t('interview.offlineInterview') }}</span>
-                </span>
-                <!-- 面试类型: 线上面试 -->
-                <span v-else class="d-flex">
-                  <v-icon class="mx-3 mt-2" size="20" color="primary">mdi mdi-video-account</v-icon>
-                  <span class="d-flex flex-column">
-                    <span>{{ $t('interview.onlineInterview') }}</span>
-                    <span style="color: var(--color-999);">腾讯会议</span>
-                  </span>
-                </span>
-              </div>
-              <!-- 面试状态: '待接受'/'已取消' -->
-              <div :style="{ 'color': item.status !== '99' ? 'orange' :'var(--color-999)'}">
-                <v-icon size="30">mdi mdi-circle-small</v-icon>
-                <span>{{ statusList.find(e => e.value === item.status)?.label }}</span>
-              </div>
-              <div>
-                <span v-if="editStatus.indexOf(item.status) !== -1" class="font-size-15 color-primary" @click="handleActionClick(2, item)">修改面试</span>
-                <span v-if="againStatus.indexOf(item.status) !== -1" class="font-size-15 color-primary" @click="handleActionClick(2, item)">重新邀约</span>
-                <v-menu>
-                  <template v-slot:activator="{ props }">
-                    <v-icon v-bind="props" class="mx-3" size="20" color="primary">mdi-dots-horizontal</v-icon>
-                  </template>
-                  <v-list>
-                    <v-list-item
-                      v-for="(k, index) in actionItems(item.status)"
-                      :key="index"
-                      :value="index"
-                      color="primary"
-                      @click="handleActionClick(k.value, item)"
-                    >
-                      <v-list-item-title>{{ k.title }}</v-list-item-title>
-                    </v-list-item>
-                  </v-list>
-                </v-menu>
-              </div>
-            </div>
-          </div>
+          <itemPage :items="items" :statusList="statusList" :positionItems="positionItems" @refresh="handleRefresh"></itemPage>
           <CtPagination
             v-if="total > 0"
             :total="total"
@@ -129,45 +71,20 @@
       </div>
     </div>
   </v-card>
-
-  <!-- 修改面试 -->
-  <CtDialog :visible="showInvite" :widthType="2" titleClass="text-h6" title="面试信息" @close="handleClose" @submit="handleSubmit">
-    <InvitePage v-if="showInvite" ref="inviteRef" :itemData="itemData" :position="positionItems"></InvitePage>
-  </CtDialog>
-
-  <CtDialog :visible="cancelInvite" :widthType="2" titleClass="text-h6" title="取消面试" @close="handleCancelClose" @submit="handleCancelSubmit">
-    <TextInput v-model="cancelQuery.reason" :item="textItems"></TextInput>
-  </CtDialog>
 </template>
 
 <script setup>
 defineOptions({ name: 'enterprise-interview'})
 import { ref } from 'vue'
-import { getInterviewInvitePage, saveInterviewInvite, cancelInterviewInvite } from '@/api/recruit/enterprise/interview'
-import InvitePage from './components/invite.vue'
+import { getInterviewInvitePage } from '@/api/recruit/enterprise/interview'
 import { getDict } from '@/hooks/web/useDictionaries'
-import Snackbar from '@/plugins/snackbar'
 import { getJobAdvertised } from '@/api/enterprise'
 import { dealDictArrayData } from '@/utils/position'
 import { timesTampChange, getStartAndEndOfDay } from '@/utils/date'
-import cloneDeep from 'lodash/cloneDeep'
+import itemPage from './components/item.vue'
 
-const cancelInvite = ref(false)
-const showInvite = ref(false)
-const inviteRef = ref()
 const items = ref([])
-const cancelQuery = ref({
-  id: null,
-  reason: null
-})
-const editStatus = ['1', '0'] // 修改面试状态
-const againStatus = ['98', '99'] // 重新邀约状态
 const statusList = ref()
-const itemData = ref({})
-const actions = ref([
-  { title: '沟通', value: 1 },
-  { title: '面试记录', value: 4 }
-])
 const total = ref(0)
 const query = ref({
   pageSize: 10,
@@ -176,19 +93,6 @@ const query = ref({
   jobId: null,
   time: []
 })
-const textItems = ref({
-  type: 'text',
-  label: '取消原因 *',
-  clearable: true
-})
-
-// 操作状态
-// status:0待接受 1待面试 2即将面试 3已完成 4已反馈 5未能爽约 98拒绝 99已取消
-const actionItems = (status) => {
-  const data = cloneDeep(actions.value)
-  if (status === '0') data.splice(-1, 0, { title: '取消面试', value: 3 })
-  return data
-}
 
 // 状态字典
 const getStatusList = async () => {
@@ -205,6 +109,11 @@ const getData = async () => {
 }
 getData()
 
+const handleRefresh = () => {
+  query.value.pageNo = 1
+  getData()
+}
+
 // 分页
 const handleChangePage = (e) => {
   query.value.pageNo = e
@@ -256,72 +165,7 @@ const getPositionList = async () => {
   })
 }
 getPositionList()
-
-// 操作按钮
-const handleActionClick = (value, item) => {
-  // 修改、重新邀约
-  if (value === 2) {
-    itemData.value = item
-    showInvite.value = true
-  }
-  // 取消
-  if (value === 3) {
-    cancelQuery.value.id = item.id
-    cancelInvite.value = true
-  }
-}
-
-// 修改面试
-const handleClose = () => {
-  itemData.value = {}
-  showInvite.value = false
-}
-
-const handleSubmit = async () => {
-  const query = inviteRef.value.getQuery()
-  if (!Object.keys(query).length) return
-  await saveInterviewInvite(query)
-  Snackbar.success('操作成功')
-  handleClose()
-  getData()
-}
-
-// 取消面试
-const handleCancelClose = () => {
-  cancelInvite.value = false
-  cancelQuery.value = {
-    id: null,
-    reason: null
-  }
-}
-
-const handleCancelSubmit = async () => {
-  if (!cancelQuery.value.reason) return Snackbar.warning('请填写取消原因')
-  await cancelInterviewInvite(cancelQuery.value)
-  Snackbar.success('操作成功')
-  handleCancelClose()
-  getData()
-}
 </script>
 
 <style scoped lang="scss">
-.listItem {
-  cursor: pointer;
-  width: 100%;
-  min-width: 600px;
-  overflow: auto;
-  height: 76px;
-  border: 1px solid #e5e6eb;
-  border-radius: 5px;
-  
-  &:hover {
-    background-color: var(--color-f8);
-  }
-  .right-item {
-    width: 100%;
-    div {
-      width: 25%;
-    }
-  }
-}
 </style>

+ 102 - 0
src/views/recruit/enterprise/personnelManagement/components/invite.vue

@@ -0,0 +1,102 @@
+<template>
+  <CtForm ref="CtFormRef" :items="formItems" style="height: 420px;">
+    <template #time="{ item }">
+      <VueDatePicker 
+        v-model="item.value"
+        placeholder="面试时间 *"
+        class="mb-4"
+        model-type="timestamp"
+        :text-input="{ format: 'MM.dd.yyyy HH:mm' }" />
+    </template>
+  </CtForm>
+</template>
+
+<script setup>
+defineOptions({ name: 'formPage'})
+import { ref } from 'vue'
+
+const props = defineProps({
+  itemData: {
+    type: Object,
+    default: () => {}
+  }
+})
+
+const CtFormRef = ref()
+const formItems = ref({
+  options: [
+    {
+      slotName: 'time',
+      key: 'time',
+      value: null,
+      rules: [v => !!v || '请选择面试时间'],
+    },
+    {
+      type: 'text',
+      key: 'position',
+      value: '',
+      noParam: true,
+      disabled: true,
+      label: '面试岗位'
+    },
+    {
+      type: 'text',
+      key: 'address',
+      value: '',
+      label: '面试地点 *',
+      rules: [v => !!v || '请输入面试地点'],
+    },
+    {
+      type: 'text',
+      key: 'invitePhone',
+      value: null,
+      label: '联系电话 *',
+      outlined: true,
+      rules: [v => !!v || '请填写联系电话']
+    },
+    {
+      type: 'textarea',
+      key: 'remark',
+      value: '',
+      label: '备注事项',
+      counter: 140,
+      rules: [
+        value => {
+          if (value?.length <= 140) return true
+          return '请输入备注事项,最多140字'
+        }
+      ]
+    }
+  ]
+})
+
+if (Object.keys(props.itemData).length) {
+  const obj = formItems.value.options.find(e => e.key === 'position')
+  obj.value = `${props.itemData?.job?.name}${props.itemData?.job?.areaName ? '_' + props.itemData?.job?.areaName : ''} ${props.itemData?.job?.payFrom}-${props.itemData?.job?.payTo}/${props.itemData?.job?.payName}`
+  formItems.value.options.find(e => e.key === 'address').value = props.itemData.job?.address
+}
+
+const getQuery = () => {
+  const obj = {
+    type: 1,
+    jobId: props.itemData.job.id,
+    userId: props.itemData.userId,
+    latitude: props.itemData.job?.latitude,
+    longitude: props.itemData.job?.longitude
+  }
+  formItems.value.options.forEach(item => {
+    if (item.noParam) return
+    obj[item.key] = item.value
+  })
+  return obj
+}
+
+defineExpose({
+  CtFormRef,
+  getQuery
+})
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 45 - 24
src/views/recruit/enterprise/personnelManagement/components/table.vue

@@ -1,8 +1,5 @@
 <template>
   <div>
-    <div class="text-end">
-      <v-btn v-if="tab === 1" color="primary" :disabled="selected.length ? false : true" variant="tonal" @click="handleAction('all', 0)">入职</v-btn>
-    </div>
     <v-data-table
       class="mt-3"
       v-model="selected"
@@ -29,16 +26,21 @@
       </template>
       <template v-slot:item.actions="{ item }">
         <div v-if="tab === 0">
-          <v-btn color="primary" variant="text" @click="handlePreviewResume(item)">查看简历</v-btn>
+          <v-btn color="primary" variant="text" @click="handlePreviewResume(item)">查看附件</v-btn>
+          <v-btn color="primary" variant="text" @click="handleInterviewInvite(item)">邀请面试</v-btn>
         </div>
         <v-btn v-if="tab === 0 || tab === 1" color="primary" variant="text" @click="handleEliminate(item)">不合适</v-btn>
         <div v-if="tab === 1">
-          <!-- <v-btn color="primary" variant="text" @click="handleInterviewInvite(item)">邀请面试</v-btn> -->
-          <v-btn color="primary" variant="text" @click="handleAction('', 0, item)">入职</v-btn>
+          <v-btn color="primary" variant="text" @click="handleEnterByEnterprise(item)">入职</v-btn>
         </div>
-        <v-btn v-if="tab === 4" color="primary" variant="text" @click="handleCancelEliminate(item)">取消不合适</v-btn>
+        <v-btn v-if="tab === 3" color="primary" variant="text" @click="handleCancelEliminate(item)">取消不合适</v-btn>
       </template>
     </v-data-table>
+
+    <!-- 邀请面试 -->
+    <CtDialog :visible="showInvite" :widthType="2" titleClass="text-h6" title="面试信息" @close="handleEditClose" @submit="handleEditSubmit">
+      <InvitePage v-if="showInvite" ref="inviteRef" :itemData="itemData"></InvitePage>
+    </CtDialog>
   </div>
 </template>
 
@@ -47,14 +49,17 @@ defineOptions({ name: 'table-page'})
 import { ref, computed, watch } from 'vue'
 import { previewFile } from '@/utils'
 import { personJobCvLook, joinEliminate, personEntryByEnterprise, personCvUnfitCancel } from '@/api/recruit/enterprise/personnel'
+import { saveInterviewInvite } from '@/api/recruit/enterprise/interview'
 import { useI18n } from '@/hooks/web/useI18n'
 import Snackbar from '@/plugins/snackbar'
+import InvitePage from './invite.vue'
 
 const { t } = useI18n()
 const emit = defineEmits(['refresh'])
 const props = defineProps({
   tab: Number,
-  items: Array
+  items: Array,
+  statusList: Array
 })
 const badgeColor = computed(() => (item) => {
   return (item.person && item.person.sex) ? (item.person.sex === '1' ? '#1867c0' : 'error') : 'error'
@@ -64,6 +69,8 @@ const badgeIcon = computed(() => (item) => {
   return (item.person && item.person.sex) ? (item.person.sex === '1' ? 'mdi-gender-male' : 'mdi-gender-female') : 'mdi-gender-female'
 })
 
+const inviteRef = ref()
+const showInvite = ref(false)
 const selected = ref([])
 const headers = ref([
   { title: '姓名', value: 'name', sortable: false },
@@ -72,20 +79,21 @@ const headers = ref([
   { title: '工作经验', key: 'person.expName', sortable: false },
   { title: '最高学历', key: 'person.eduName', sortable: false },
   { title: '岗位薪资', key: 'job', value: item => `${item.job.payFrom}-${item.job.payTo}/${item.job.payName}`, sortable: false },
+  { title: '状态', key: 'status', sortable: false, value: item => item.status ? props.statusList.find(i => i.value === item.status).label : '' },
   { title: '操作', value: 'actions', sortable: false }
 ])
 const unfit = { title: '类型', key: 'unfitType', sortable: false, value: item => item.type === '0' ? '简历不合适' : '面试不合适' }
 const delivery = { title: '类型', key: 'deliveryType', sortable: false, value: item => item.status === '0' ? '新投递' : '已查看' }
 
-const list = [0, 4]
+const list = [0, 3]
 watch(
   () => props.tab,
   (val) => {
     if (list.indexOf(val) !== -1) {
       headers.value.splice(-1, 0, val === 0 ? delivery : unfit)
     } else {
-      const index = headers.value.findIndex(item => item.key === val === 0 ? 'deliveryType' : 'unfitType')
-      if (index > -1) headers.value.splice(index, 1)
+      const index = headers.value.indexOf(item => item.key === val === 0 ? 'deliveryType' : 'unfitType')
+      if (index !== -1) headers.value.splice(index, 1)
     }
   },
   { immediate: true }
@@ -97,15 +105,10 @@ const handleToPersonDetail = ({ userId, id }) => {
   window.open(`/recruit/enterprise/talentPool/details/${userId}?id=${id}`)
 }
 
-const apiList = [
-  personEntryByEnterprise // 入职
-]
-
 // 入职
-const handleAction = async (type, index, item) => {
-  const ids = type ? selected.value : [item?.id]
-  if (!ids) return
-  await apiList[index](ids)
+const handleEnterByEnterprise = async (item) => {
+  if (!item.id) return
+  await personEntryByEnterprise(item.id)
   Snackbar.success(t('common.operationSuccessful'))
   emit('refresh')
 }
@@ -139,9 +142,28 @@ const handlePreviewResume = async ({ url, id }) => {
   previewFile(url)
 }
 
-// const handleInterviewInvite = (item) => {
-//   console.log(item, 'item')
-// }
+// 邀请面试
+const itemData = ref({})
+const handleInterviewInvite = (item) => {
+  itemData.value = item
+  showInvite.value = true
+}
+
+const handleEditClose = () => {
+  showInvite.value = false
+  itemData.value = {}
+}
+
+const handleEditSubmit = async () => {
+  const { valid } = await inviteRef.value.CtFormRef.formRef.validate()
+  if (!valid) return
+  const query = inviteRef.value.getQuery()
+  if (!query?.time) return Snackbar.warning('请选择面试时间')
+  await saveInterviewInvite(query)
+  Snackbar.success(t('common.operationSuccessful'))
+  handleEditClose()
+  emit('refresh')
+}
 </script>
 
 <style scoped lang="scss">
@@ -149,7 +171,6 @@ const handlePreviewResume = async ({ url, id }) => {
   background-color: #f7f8fa !important;
 }
 :deep(.v-selection-control__input) {
-  // color: var(--v-primary-base) !important;
-  color: #767778;
+  color: var(--v-primary-base) !important;
 }
 </style>

+ 10 - 4
src/views/recruit/enterprise/personnelManagement/index.vue

@@ -11,7 +11,7 @@
 
     <v-window v-model="tab" class="mt-1">
       <v-window-item v-for="k in tabList" :value="k.value" :key="k.value">
-        <TablePage :items="items" :tab="k.value" @refresh="getList"></TablePage>
+        <TablePage :items="items" :tab="k.value" :statusList="statusList" @refresh="getList"></TablePage>
         <CtPagination
           v-if="total > 0"
           :total="total"
@@ -30,6 +30,7 @@ import { ref } from 'vue'
 import { getPersonCvPage } from '@/api/enterprise'
 import { personCvUnfitPage } from '@/api/recruit/enterprise/personnel'
 import { dealDictObjData } from '@/utils/position'
+import { getDict } from '@/hooks/web/useDictionaries'
 import { getInterviewInvitePage } from '@/api/recruit/enterprise/interview'
 import TablePage from './components/table.vue'
 import Screen from './components/screen.vue'
@@ -45,9 +46,8 @@ const tab = ref(0)
 const tabList = ref([
   { label: '投递简历', value: 0, api: getPersonCvPage, status: null },
   { label: '已邀约', value: 1, api: getInterviewInvitePage, status: '0' },
-  { label: '已发offer', value: 2, api: getInterviewInvitePage, status: '1' },
-  { label: '已入职', value: 3, api: getInterviewInvitePage, status: '2' },
-  { label: '不合适', value: 4, api: personCvUnfitPage },
+  { label: '已入职', value: 2, api: getInterviewInvitePage, status: '2' },
+  { label: '不合适', value: 3, api: personCvUnfitPage },
 ])
 const textItems = ref({
   type: 'text',
@@ -58,6 +58,12 @@ const textItems = ref({
   appendInnerIcon: 'mdi-magnify'
 })
 
+// 状态字典
+const statusList = ref([])
+getDict('menduner_interview_invite_status').then(({data}) => {
+  if (data && data.length) statusList.value = data
+})
+
 // 获取牛人列表
 const items = ref([])
 const getList = async () => {

+ 57 - 24
src/views/recruit/enterprise/publicRecruitmentManagement/deliver/components/table.vue

@@ -1,11 +1,16 @@
 <template>
   <div>
-    <div class="text-end">
-      <v-btn v-if="tab === '2'" color="primary" :disabled="selected.length ? false : true" variant="tonal" @click="handleAction('all', 0, {})">不合适</v-btn>
-      <v-btn v-if="tab === '3'" color="primary" :disabled="selected.length ? false : true" variant="tonal" @click="handleAction('all', 1, {})">入职</v-btn>
+    <div class="mr-5 d-flex align-center" v-if="tab === 0">
+      <div style="width: 200px;">
+        <v-radio-group v-model="radio" inline style="height: 28px;" @update:modelValue="handleChangeSelected">
+          <v-radio v-model="radio" label="已报名" value="0" hide-details density="compact" class="mr-3"></v-radio>
+          <v-radio v-model="radio" label="已查看" value="1" hide-details density="compact"></v-radio>
+        </v-radio-group>
+      </div>
+      <div class="reset-text cursor-pointer ml-3" @click="handleReset">重置</div>
     </div>
     <v-data-table
-      class="mt-3"
+      class="mt-5"
       v-model="selected"
       :items="items"
       :headers="headers"
@@ -28,12 +33,12 @@
           <span class="defaultLink ml-3">{{ item?.person?.name }}</span>
         </div>
       </template>
-      <!-- 已报名:查看简历,已邀约:不合适,已发offer:入职,已入职:结算 -->
       <template v-slot:item.actions="{ item }">
-        <v-btn v-if="tab === '0' && item.url" color="primary" variant="text" @click="handlePreviewResume(item)">查看附件</v-btn>
-        <v-btn v-if="tab === '2'" color="primary" variant="text" @click="handleAction('', 0, item)">不合适</v-btn>
-        <v-btn v-if="tab === '1'" color="primary" variant="text" @click="handleAction('', 1, item)">入职</v-btn>
-        <v-btn v-if="tab === '4'" color="primary" variant="text" @click="handleSettlement(item)">结算</v-btn>
+        <v-btn v-if="tab === 0 && item.url" color="primary" variant="text" @click="handlePreviewResume(item)">查看附件</v-btn>
+        <v-btn v-if="tab === 1 || tab === 0" color="primary" variant="text" @click="handleEliminate(item)">不合适</v-btn>
+        <v-btn v-if="tab === 2" color="primary" variant="text" @click="handleEnterByEnterprise(item)">入职</v-btn>
+        <v-btn v-if="tab === 3" color="primary" variant="text" @click="handleSettlement(item)">结算</v-btn>
+        <v-btn v-if="radio === '1'" color="primary" variant="text">邀请面试</v-btn>
       </template>
     </v-data-table>
   </div>
@@ -42,17 +47,19 @@
 <script setup>
 defineOptions({ name: 'table-page'})
 import { ref, computed } from 'vue'
-import { hireJobCvRelEntry, hireJobCvRelEliminate, hireJobCvRelSettlement, hireJobCvRelLook } from '@/api/recruit/public/delivery'
+import { hireJobCvRelSettlement, hireJobCvRelLook } from '@/api/recruit/public/delivery'
+import { joinEliminate, personEntryByEnterprise } from '@/api/recruit/enterprise/personnel'
 import { previewFile } from '@/utils'
 import { useI18n } from '@/hooks/web/useI18n'
 import Snackbar from '@/plugins/snackbar'
 import { useUserStore } from '@/store/user'
 
 const { t } = useI18n()
+const radio = ref()
 const userStore = useUserStore()
-const emit = defineEmits(['refresh'])
+const emit = defineEmits(['refresh', 'radio', 'reset'])
 const props = defineProps({
-  tab: String,
+  tab: Number,
   items: Array,
   tabList: Array
 })
@@ -73,7 +80,7 @@ const headers = ref([
   { title: '最高学历', key: 'person.eduName', sortable: false },
   { title: '岗位薪资', key: 'job', value: item => `${item.job.payFrom}-${item.job.payTo}/${item.job.payName}`, sortable: false },
   { title: '推荐人', key: 'recommendPerson', value: item => item?.recommendPerson?.name, sortable: false },
-  { title: '进度', key: 'status', sortable: false, value: item => props.tabList.find(e => e.value === item.status).label },
+  { title: '进度', key: 'status', sortable: false, value: item => props.tabList.find(e => e.value === Number(item.status)).label },
   { title: '结果反馈', key: '', sortable: false },
   { title: '操作', value: 'actions' }
 ])
@@ -84,16 +91,24 @@ const handleToPersonDetail = ({ userId, id }) => {
   window.open(`/recruit/enterprise/talentPool/details/${userId}?id=${id}`)
 }
 
-const apiList = [
-  hireJobCvRelEliminate, // 不合适
-  hireJobCvRelEntry // 入职
-]
+// 入职
+const handleEnterByEnterprise = async (item) => {
+  if (!item.id) return
+  await personEntryByEnterprise(item.id)
+  Snackbar.success(t('common.operationSuccessful'))
+  emit('refresh')
+}
 
-// 不合适、入职
-const handleAction = async (type, index, item) => {
-  const ids = type ? selected.value : [item?.id]
-  if (!ids) return
-  await apiList[index](ids)
+// 不合适
+const handleEliminate = async (item) => {
+  if (!item.id || !item?.job?.id) return
+  const query = {
+    bizId: item.id,
+    jobId: item.job.id,
+    userId: item.userId,
+    type: props.tab === 0 ? '0' : '1' // 投递简历0 已邀约1
+  }
+  await joinEliminate(query)
   Snackbar.success(t('common.operationSuccessful'))
   emit('refresh')
 }
@@ -116,6 +131,16 @@ const handlePreviewResume = async ({ url, id }) => {
   await hireJobCvRelLook(id)
   previewFile(url)
 }
+
+// 已报名、已查看
+const handleChangeSelected = (e) => {
+  emit('radio', e)
+}
+
+const handleReset = () => {
+  radio.value = ''
+  emit('reset')
+}
 </script>
 
 <style scoped lang="scss">
@@ -123,7 +148,15 @@ const handlePreviewResume = async ({ url, id }) => {
   background-color: #f7f8fa !important;
 }
 :deep(.v-selection-control__input) {
-  // color: var(--v-primary-base) !important;
-  color: #767778;
+  color: var(--v-primary-base) !important;
+  // color: #767778;
+}
+
+.reset-text {
+  font-size: 14px;
+  color: var(--color-666);
+  &:hover {
+    color: var(--v-primary-base);
+  }
 }
 </style>

+ 27 - 16
src/views/recruit/enterprise/publicRecruitmentManagement/deliver/index.vue

@@ -1,15 +1,15 @@
 <template>
   <v-card class="pa-5 card-box">
     <div class="d-flex justify-space-between">
-      <v-tabs v-model="query.status" align-tabs="start" color="primary" bg-color="#f7f8fa" @update:model-value="handleUpdateTab">
+      <v-tabs v-model="tab" align-tabs="start" color="primary" bg-color="#f7f8fa" @update:model-value="handleUpdateTab">
         <v-tab v-for="k in tabList" :value="k.value" :key="k.value">{{ k.label }}</v-tab>
       </v-tabs>
       <TextInput v-model="textItems.value" :item="textItems" @appendInnerClick="handleSearch" @enter="handleSearch"></TextInput>
     </div>
 
-    <v-window v-model="query.status" class="mt-1">
+    <v-window v-model="tab" class="mt-1">
       <v-window-item v-for="k in tabList" :value="k.value" :key="k.value">
-        <TablePage :items="items" :tab="k.value" :tabList="tabList" @refresh="getList"></TablePage>
+        <TablePage :items="items" :tab="k.value" :tabList="tabList" @refresh="getList" @radio="handleSelect" @reset="handleReset"></TablePage>
         <CtPagination
           v-if="total > 0"
           :total="total"
@@ -25,7 +25,6 @@
 <script setup>
 defineOptions({ name: 'public-recruitment-deliver'})
 import { ref } from 'vue'
-import { getDict } from '@/hooks/web/useDictionaries'
 import { getHireJobCvPage } from '@/api/recruit/public/delivery'
 import { dealDictObjData } from '@/utils/position'
 import TablePage from './components/table.vue'
@@ -34,10 +33,17 @@ const total = ref(0)
 const query = ref({
   pageNo: 1,
   pageSize: 10,
-  status: '0'
+  status: null
 })
+const tab = ref(0)
 const items = ref([])
-const tabList = ref([])
+const tabList = ref([
+  { label: '简历投递', value: 0, status: null },
+  { label: '已邀约', value: 1, status: 2 },
+  { label: '已入职', value: 2, status: 3 },
+  { label: '已结算', value: 3, status: 4 },
+  { label: '不合适', value: 4, status: 99 }
+])
 const textItems = ref({
   type: 'text',
   value: '',
@@ -47,15 +53,6 @@ const textItems = ref({
   appendInnerIcon: 'mdi-magnify'
 })
 
-// 获取tab列表项
-const getTabData = () => {
-  getDict('menduner_hire_job_cv_status').then(({ data }) => {
-    data = data?.length && data || []
-    tabList.value = data
-  })
-}
-getTabData()
-
 const getList = async () => {
   const res = await getHireJobCvPage(query.value)
   if (!res.list.length) {
@@ -80,7 +77,22 @@ const handleSearch = () => {
   getList()
 }
 
+// 已查看、已报名查看
+const handleSelect = (e) => {
+  query.value.status = e
+  query.value.pageNo = 1
+  getList()
+}
+
+const handleReset = () => {
+  query.value.status = null
+  query.value.pageNo = 1
+  getList()
+}
+
+// tab
 const handleUpdateTab = () => {
+  query.value.status = tabList.value.find(e => e.value === tab.value).status
   query.value.pageNo = 1
   getList()
 }
@@ -93,5 +105,4 @@ const handleChangePage = (i) => {
 </script>
 
 <style scoped lang="scss">
-
 </style>

+ 8 - 8
src/views/recruit/enterprise/statistics/components/overview.vue

@@ -19,14 +19,14 @@ import * as echarts from 'echarts'
 
 // 数据概况
 const overview = ref([
-  { title: '我看过', value: 86, desc: '' },
-  { title: '看过我', value: 12, desc: '' },
-  { title: '我打招呼', value: 0, desc: '' },
-  { title: '牛人新招呼', value: 4, desc: '' },
-  { title: '我沟通', value: 0, desc: '' },
-  { title: '收获简历', value: 5, desc: '' },
-  { title: '交换电话微信', value: 2, desc: '' },
-  { title: '接受面试', value: 0, desc: '' }
+  { title: '职位浏览量', value: 86, desc: '' },
+  { title: '收到简历量', value: 12, desc: '' },
+  { title: '查看收到简历', value: 0, desc: '' },
+  { title: '已处理简历', value: 4, desc: '' },
+  { title: '主动联系我的人', value: 0, desc: '' },
+  { title: '我主动联系的人', value: 5, desc: '' },
+  // { title: '交换电话微信', value: 2, desc: '' },
+  { title: '面试数量', value: 0, desc: '' }
 ])
 
 onMounted(() => {

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

@@ -16,7 +16,7 @@
     </div>
     <div class="mt-10">
       <v-tabs v-model="tab" align-tabs="start" color="primary" bg-color="#f7f8fa">
-        <v-tab :value="1">数据概况</v-tab>
+        <v-tab :value="1">招聘进展</v-tab>
       </v-tabs>
       <Overview class="mt-5"></Overview>
     </div>

+ 1 - 1
src/views/recruit/enterprise/systemManagement/groupAccount/inviteConfirm.vue

@@ -77,7 +77,7 @@ const handleLogin = async () => {
     const res = await smsLogin(params)
     setToken(res.accessToken)
     setRefreshToken(res.refreshToken)
-    localStorage.setItem('loginType', 'personal') // 不存在时刷新会出现重定向,值没有影响
+    localStorage.setItem('loginType', 'personal') // 不存在时刷新会出现重定向
     getUserBaseInfos(res.userId)
   } catch (error) {
     Snackbar.error('加入失败! ' + error)

+ 47 - 6
src/views/recruit/personal/PersonalCenter/components/interview/index.vue

@@ -1,13 +1,23 @@
 <template>
   <div>
     <v-divider></v-divider>
-      <v-tabs v-model="tabVal" align-tabs="start" color="primary" bg-color="#fff">
+      <v-tabs class="mb-3" v-model="query.status" align-tabs="start" color="primary" bg-color="#fff" @update:model-value="handleUpdate">
         <v-tab :value="0">待接受</v-tab>
         <v-tab :value="1">待面试</v-tab>
-        <v-tab :value="2">已完成</v-tab>
-        <v-tab :value="3">已取消</v-tab>
+        <v-tab :value="3">已完成</v-tab>
+        <v-tab :value="98">已拒绝</v-tab>
       </v-tabs>
-      <Empty class="mt-3"></Empty>
+      <div v-if="items.length">
+        <ItemPage :items="items" @refresh="getData"></ItemPage>
+        <CtPagination
+          v-if="total > 0"
+          :total="total"
+          :page="query.pageNo"
+          :limit="query.pageSize"
+          @handleChange="handleChangePage"
+        ></CtPagination>
+      </div>
+      <Empty v-else class="mt-3"></Empty>
   </div>
 </template>
 
@@ -15,9 +25,40 @@
 // 面试
 defineOptions({ name: 'interview-index'})
 import { ref } from 'vue'
-// import { getUserInterviewInvitePage } from '@/api/recruit/personal/personalCenter'
+import { getUserInterviewInvitePage } from '@/api/recruit/personal/personalCenter'
+import { dealDictObjData } from '@/utils/position'
+import ItemPage from './item.vue'
+
+const query = ref({
+  pageNo: 1,
+  pageSize: 10,
+  status: 0
+})
+const total = ref(0)
+const items = ref([])
+
+const getData = async () => {
+  const { list, total: number } = await getUserInterviewInvitePage(query.value)
+  items.value = list.map(e => {
+    e.job = { ...e.job, ...dealDictObjData({}, e.job) }
+    e.enterprise = { ...e.enterprise, ...dealDictObjData({}, e.enterprise)}
+    e.active = false
+    return e
+  })
+  total.value = number
+}
+getData()
+
+const handleUpdate = () => {
+  query.value.pageNo = 1
+  getData()
+}
+
+const handleChangePage = (e) => {
+  query.value.pageNo = e
+  getData()
+}
 
-const tabVal = ref(0)
 </script>
 
 <style scoped lang="scss">

+ 184 - 0
src/views/recruit/personal/PersonalCenter/components/interview/item.vue

@@ -0,0 +1,184 @@
+<template>
+  <div class="position-item mb-3 job-closed" v-for="(val, i) in props.items" :key="i" @mouseenter="val.active = true" @mouseleave="val.active = false">
+      <div class="info-header">
+        <div v-if="val.active && val.status === '0'" class="header-btn">
+          <v-btn color="primary" size="small" @click="handleAgree(val)">同意</v-btn>
+          <v-btn class="ml-3" color="error" size="small" @click="handleRefuse(val)">拒绝</v-btn>
+        </div>
+        <div class="img-box">
+          <v-avatar :image="val.contact.avatar || 'https://minio.citupro.com/dev/menduner/7.png'" size="x-small"></v-avatar>
+          <span class="name">
+            <span class="mx-3">{{ val.contact.name }}</span>
+            <span class="gray">{{ val.contact.postNameCn }}</span>
+            <span v-if="val.invitePhone" class="septal-line"></span>
+            <span class="gray">{{ val.invitePhone }}</span>
+          </span>
+        </div>
+      </div>
+      <div class="info-content">
+        <div class="job-info">
+          <div class="job-name ellipsis" style="max-width: 410px;">
+            <span class="mr-3">{{ val.job.name }}</span>
+            <span>{{ val.job.payFrom }}-{{ val.job.payTo }}/{{ val.job.payName }}</span>
+          </div>
+          <div class="job-other d-flex align-center">
+            <div style="width: 30px;height: 30px;">
+              <v-img width="30" height="30" :src="val.enterprise.logoUrl || 'https://minio.citupro.com/dev/menduner/7.png'"></v-img>
+            </div>
+            <div class="ellipsis" style="max-width: 400px;">
+              <span class="mx-2 enterprise-name" @click="handleToEnterprise(val)">{{ val.enterprise.name }}</span>
+            [
+              <span>{{ val.enterprise.industryName }}</span>
+              <span>&nbsp;·&nbsp;{{ val.enterprise.financingName }}</span>
+              <span>&nbsp;·&nbsp;{{ val.enterprise.scaleName }}</span>
+            ]
+            </div>
+          </div>
+        </div>
+        <div class="font-size-14 color-666" style="width: 322px;">
+          <div>面试时间:{{ timesTampChange(val.time).slice(0, 16) }}</div>
+          <div class="mt-3 ellipsis" style="max-width: 322px;">面试地点:{{ val.address }}</div>
+        </div>
+      </div>
+  </div>
+
+  <CtDialog :visible="show" title="同意面试邀请" :footer="true" widthType="2" @close="handleClose" @submit="handleSubmit">
+    <TextInput v-model="query.phone" :item="textItem"></TextInput>
+  </CtDialog>
+</template>
+
+<script setup>
+defineOptions({ name: 'interview-item'})
+import { ref } from 'vue'
+import { useI18n } from '@/hooks/web/useI18n'
+import { timesTampChange } from '@/utils/date'
+import { userInterviewInviteReject, userInterviewInviteConsent } from '@/api/recruit/personal/personalCenter'
+import Snackbar from '@/plugins/snackbar'
+import Confirm from '@/plugins/confirm'
+
+const { t } = useI18n()
+const emits = defineEmits(['refresh'])
+const props = defineProps({
+  items: {
+    type: Array,
+    default: () => []
+  }
+})
+const show = ref(false)
+const query = ref({
+  id: null,
+  phone: null
+})
+const textItem = ref({
+  type: 'text',
+  clearable: true,
+  label: '联系号码 *'
+})
+
+// 企业详情
+const handleToEnterprise = (item) => {
+  const id = item.enterprise.id
+  if (!id) return
+  window.open(`/recruit/personal/company/details/${id}?key=briefIntroduction`)
+}
+
+// 同意
+const handleAgree = (val) => {
+  if (!val.id) return
+  query.value = {
+    id: val.id,
+    phone: val.phone
+  }
+  show.value = true
+}
+
+const handleClose = () => {
+  show.value = false
+  query.value = {
+    id: null,
+    phone: null
+  }
+}
+
+const handleSubmit = async () => {
+  if (!query.value.phone) return Snackbar.warning('请填写您的联系号码')
+  await userInterviewInviteConsent(query.value)
+  Snackbar.success(t('common.operationSuccessful'))
+  handleClose()
+  emits('refresh')
+}
+
+// 拒绝
+const handleRefuse = (val) => {
+  if (!val.id) return
+  Confirm(t('common.confirmTitle'), '您是否确定要拒绝此面试邀请?').then(async () => {
+    await userInterviewInviteReject(val.id)
+    Snackbar.success(t('common.operationSuccessful'))
+    emits('refresh')
+  })
+}
+</script>
+
+<style scoped lang="scss">
+.position-item {
+  height: 144px;
+  background-color: #fff;
+  border-radius: 12px;
+  &:hover {
+    box-shadow: 0 16px 40px 0 rgba(153, 153, 153, .3);
+  }
+  .info-header {
+    height: 48px;
+    background: linear-gradient(90deg,#f5fcfc,#fcfbfa);
+    border-radius: 12px;
+    .img-box {
+      padding: 12px 24px;
+      .name {
+        color: var(--color-222);
+        font-weight: 400;
+        font-size: 13px;
+        .gray {
+          color: var(--color-666);
+        }
+      }
+    }
+    .header-btn {
+      padding: 10px 10px 0 0;
+      float: right;
+      .v-btn {
+        z-index: 1;
+      }
+    }
+  }
+  .info-content {
+    display: flex;
+    padding: 16px 24px;
+    justify-content: space-between;
+    .job-info {
+      width: 430px;
+      max-width: 430px;
+      font-weight: 500;
+      font-size: 16px;
+      margin-right: 12px;
+      .job-name {
+        height: 22px;
+        line-height: 22px;
+        color: var(--color-222);
+        margin-bottom: 12px;
+      }
+      .job-other {
+        color: var(--color-666);
+        height: 22px;
+        line-height: 22px;
+        font-size: 14px;
+        .enterprise-name {
+          cursor: pointer;
+          &:hover {
+            color: var(--v-primary-base)
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 88 - 5
src/views/recruit/personal/PersonalCenter/components/interviewSchedule.vue

@@ -1,21 +1,104 @@
 <!-- 面试日程 -->
 <template>
-  <div style="height: 100%; overflow: hidden;">
-    <div class="white-bgc pa-3 pt-5 mb-3" style="font-size: 16px; display: flex; justify-content: space-between;">
+  <div style="height: 100%; overflow: hidden;background-color: var(--default-bgc);">
+    <div class="white-bgc px-3 py-5" style="font-size: 16px; display: flex; justify-content: space-between; border-bottom: 1px solid #e3e3e3;">
       <div>面试日程</div>
-      <div class="defaultLink" @click="null">查看全部</div>
+      <div class="defaultLink" @click="handleMore">查看全部</div>
     </div>
     <!-- 滚动区域 -->
-     <div class="mr-1" style="height: calc(100% - 100px); background-color: var(--default-bgc); overflow-y: auto;">
-      
+     <div class="" style="height: calc(100% - 86px); overflow-y: auto;">
+      <div v-for="val in props.dataList" :key="'schedule' + val">
+        <div class="color-666 px-8 py-3" style="">{{ timesTampChange(val?.time, 'M月D日') || '--' }}</div>
+        <div class="white-bgc pa-6">
+          <div class="mb-3 d-flex justify-space-between">
+            <div style="font-weight: bold;">{{ val?.enterprise?.anotherName || '--' }}</div>
+            <div
+              class="fz-15"
+              :style="{ 'color': val?.status.toString() === '0' ?
+                'var(--v-primary-base)': val?.status.toString() === '1' ?
+                'var(--v-primary-base)': val?.status.toString() === '2' ?
+                'var(--v-primary-base)': val?.status.toString() === '3' ?
+                'var(--v-primary-base)': val?.status.toString() === '4' ?
+                'var(--v-primary-base)': val?.status.toString() === '5' ?
+                'var(--v-error-base)': val?.status.toString() === '98' ?
+                'var(--v-error-base)': val?.status.toString() === '99' ? 'var(--v-error-base)' : ''
+              }"
+            >
+              {{ getText(val?.status+'', statusList) || '--' }}
+            </div>
+          </div>
+          <div class="fz-14 mb-2">
+            <span>时间:</span>
+            <span class="c-base">{{ timesTampChange(val?.time, 'h:m') || '--' }}</span>
+          </div>
+          <div class="fz-14 mb-2">
+            <span>职位:</span>
+            <span class="c-base">{{ val?.job?.name || '--' }}</span>
+          </div>
+          <div class="fz-14 mb-2">
+            <span>薪资:</span>
+            <span class="c-base">{{ val?.job?.payFrom || '--' }}-{{ val?.job?.payTo || '--' }}/{{ val?.job?.payName || '--' }}</span>
+          </div>
+          <div class="img-box">
+            <v-avatar size="small" :image="val.contact.avatar || 'https://minio.citupro.com/dev/menduner/7.png'"></v-avatar>
+            <span class="name">
+              <span class="mx-3">{{ val.contact.name }}</span>
+              <span class="septal-line"></span>
+              <span class="gray">{{ val.contact.postNameCn }}</span>
+            </span>
+          </div>
+        </div>
+      </div>
+      <div class="text-center color-666 my-8" style="cursor: pointer;" @click="handleMore">{{ $t('common.more') }}</div>
      </div>
   </div>
 </template>
 
 <script setup>
+import { getDict } from '@/hooks/web/useDictionaries'
+import { getText } from '@/utils/getText'
+import { timesTampChange } from '@/utils/date'
+import { ref } from 'vue'
 defineOptions({name: 'PersonalCenter-interviewSchedule'})
+const emit = defineEmits(['handleMore'])
+const props = defineProps({
+  dataList: {
+    type: Array,
+    default: () => []
+  }
+})
+
+
+// 状态字典
+const statusList = ref()
+const getStatusList = async () => {
+  const { data } = await getDict('menduner_interview_invite_status')
+  statusList.value = data
+}
+getStatusList()
+
+const handleMore = () => {
+  emit('handleMore')
+}
+
 </script>
 <style lang="scss" scoped>
+.fz-13 { font-size: 13px; }
+.fz-14 { font-size: 14px; }
+.fz-15 { font-size: 15px; }
+.c-base { color: var(--v-primary-base); }
+.img-box {
+  height: 48px;
+  padding: 12px 0;
+  .name {
+    color: var(--color-222);
+    font-weight: 400;
+    font-size: 13px;
+    .gray {
+      color: var(--color-666);
+    }
+  }
+}
 ::-webkit-scrollbar {
   width: 4px;
   height: 10px;

+ 16 - 2
src/views/recruit/personal/PersonalCenter/dynamic/left.vue

@@ -64,7 +64,7 @@
 <script setup>
 defineOptions({ name: 'personal-center-left'})
 import { getDict } from '@/hooks/web/useDictionaries'
-import { ref } from 'vue'
+import { ref, watch } from 'vue'
 import { useUserStore } from '@/store/user'
 import { updateJobStatus } from '@/api/recruit/personal/resume'
 import { useI18n } from '@/hooks/web/useI18n'
@@ -83,7 +83,21 @@ const list = [
   { title: t('position.interested'), path: interested },
   { title: t('position.haveSeenMe'), path: seenMe }
 ]
-const tab = ref(2)
+
+const tab = ref(0)
+
+import { useRoute } from 'vue-router'; const route = useRoute()
+import { useRouter } from 'vue-router'; const router = useRouter()
+watch(() => route.query, (newQuery) => { // newQuery, oldQuery
+  if (newQuery?.showInterviewScheduleMore) { // 去掉面试日程参数
+    tab.value = 2
+    const query = { ...route.query }
+    delete query.showInterviewScheduleMore
+    const path = route.path
+    router.replace({ path, query })
+  }
+})
+
 const selectVal = ref('0')
 const items = ref([])
 const userStore = useUserStore()

+ 27 - 5
src/views/recruit/personal/PersonalCenter/dynamic/right.vue

@@ -58,9 +58,9 @@
       style="height: 100vh; overflow: hidden;"
       temporary
       location="right"
-      width="700"
+      width="300"
     >
-      <interviewSchedule></interviewSchedule>
+      <interviewSchedule :dataList="invitePageList" @handleMore="interviewScheduleMore()"></interviewSchedule>
     </v-navigation-drawer>
   </div>
 </template>
@@ -71,12 +71,15 @@ import { ref } from 'vue'
 import { uploadFile } from '@/api/common'
 import { previewFile } from '@/utils'
 import { useRouter } from 'vue-router'
+import { useRoute } from 'vue-router'; const route = useRoute()
 import { getPersonResumeCv, savePersonResumeCv, deletePersonResumeCv } from '@/api/recruit/personal/resume'
 import { useI18n } from '@/hooks/web/useI18n'
 import { useUserStore } from '@/store/user'
 import Snackbar from '@/plugins/snackbar'
 import Confirm from '@/plugins/confirm'
 import interviewSchedule from './../components/interviewSchedule.vue'
+import { getUserInterviewInvitePage } from '@/api/recruit/personal/personalCenter'
+import { dealDictObjData } from '@/utils/position'
 
 const { t } = useI18n()
 const router = useRouter()
@@ -91,11 +94,11 @@ userStore.$subscribe((mutation, state) => {
   userAccount.value = state.userAccount || {}
 })
 
-const resumeList = [
+const resumeList = ref([
   // { icon: 'mdi-upload', title: t('resume.topResume'), desc: t('resume.increaseMoreExposure') },
   { name: 'refresh', icon: 'mdi-refresh', title: t('resume.refreshResume'), desc: t('resume.enhanceResumeActivity') },
-  { name: 'interview', icon: 'mdi-account-multiple-check-outline', title: t('resume.interviewSchedule'), desc: '有2个待面试' },
-]
+  { name: 'interview', icon: 'mdi-account-multiple-check-outline', title: t('resume.interviewSchedule'), desc: '' },
+])
 const showInterviewSchedule = ref(false)
 const resumeClick = (val) => {
   if (val.name === 'interview') {
@@ -179,6 +182,25 @@ const handleDownload = (k) => {
     saveAs(blob, k.title)
   })
 }
+
+// 面试日程
+const invitePageList = ref([])
+const getUserInterviewInvitePageList = async () => {
+  const res = await getUserInterviewInvitePage()
+  invitePageList.value = res?.list.map(e => {
+    e.job = { ...e.job, ...dealDictObjData({}, e.job) }
+    e.enterprise = { ...e.enterprise, ...dealDictObjData({}, e.enterprise)}
+    return e
+  }) || []
+  const interview = resumeList.value.find(f => f.name === 'interview')
+  if (interview) interview.desc = '有' + (res?.total || '0') + '个待面试'
+}
+getUserInterviewInvitePageList()
+const interviewScheduleMore = () => {
+  showInterviewSchedule.value = false
+  const path = route.path
+  router.push({ path, query: { showInterviewScheduleMore: true } })
+}
 </script>
 
 <style scoped lang="scss">

+ 10 - 0
src/views/recruit/personal/home/index.vue

@@ -13,10 +13,13 @@
       <PopularEnterprises class="mt-10"></PopularEnterprises>
     </div>
   </div>
+  <!-- 快速填写简易人才信息-弹窗 -->
+  <simplePage v-if="showSimplePage" :closeable="true"></simplePage>
 </template>
 
 <script setup>
 defineOptions({ name:'personal-index'})
+import simplePage from '@/views/recruit/personal/shareJob/sendResume/simple.vue'
 import headCarousel from './components/headCarousel.vue'
 import headSearch from '@/components/headSearch'
 import hotJobs from './components/hotJobs.vue'
@@ -26,8 +29,15 @@ import hotPromotedPositions from './components/hotPromotedPositions.vue'
 import PopularEnterprises from './components/popularEnterprises.vue'
 import advertisementPage from './components/advertisement.vue'
 import { useRouter } from 'vue-router'
+import { nextTick, ref } from 'vue'
 
 const router = useRouter()
+console.log('1', localStorage.getItem('simpleCompleteDialogHaveBeenShow'))
+const simple = localStorage.getItem('simpleCompleteDialogHaveBeenShow')
+const showSimplePage = ref(simple? false : true) // 只提示一次
+nextTick(() => {
+  localStorage.setItem('simpleCompleteDialogHaveBeenShow', true)
+})
 
 const handleSearch = (val) => {
   if (val) router.push(`/recruit/personal/position?content=${val}`)

+ 6 - 20
src/views/recruit/personal/remuse/components/basicInfo.vue

@@ -117,8 +117,6 @@
 <script setup>
 import CtForm from '@/components/CtForm'
 import Snackbar from '@/plugins/snackbar'
-// import areaType from '@/components/AreaSelect'
-// import textUI from '@/components/FormUI/TextInput'
 import { getDict } from '@/hooks/web/useDictionaries'
 import { cityToProvince } from '@/utils/areaDeal'
 import { getTimeStamp, timesTampChange } from '@/utils/date'
@@ -137,7 +135,9 @@ const isExpand = ref(false)
 const welfareList = ref(['响应', '改变', '诚信', '进取精神', '信任', '卓越'])
 let baseInfo = ref({})
 const getBasicInfo = () => { // 获取基础信息
-  baseInfo.value = JSON.parse(localStorage.getItem('baseInfo')) || {} // 人才信息
+  const key = localStorage.getItem('baseInfo')
+  if (!key || !Object.keys(key).length) return
+  baseInfo.value = JSON.parse(key) // 人才信息
 }
 getBasicInfo()
 
@@ -364,24 +364,11 @@ const handleSave = async () => {
   Snackbar.success(t('common.saveMsg'))
   isEdit.value = false
   // 获取当前登录账户信息
-  if (baseInfo.value.userId) await userStore.getUserBaseInfos(baseInfo.value.userId)
-  await getBasicInfo()
+  // if (baseInfo.value.userId) await userStore.getUserBaseInfos(baseInfo.value.userId)
+  await userStore.getUserBaseInfos(baseInfo.value.userId || null)
+  getBasicInfo()
 }
 
-// 城市
-// const setValue = (key, id, name) => {
-//   const item =  items.value.options.find(e => e.key === key)
-//   if (item) {
-//     item.value = id
-//     item[item.nameKey] = name
-//   }
-// }
-// const handleArea = (list, name) => {
-//   if (!list.length) return
-//   const id = list[0]
-//   setValue('areaId', id, name)
-// }
-
 // 获取字典内容
 const getDictData = async (dictTypeName) => {
   const item = items.value.options.find(e => e.dictTypeName === dictTypeName)
@@ -399,7 +386,6 @@ const deal = async () => {
       const city = items.value.options.find(pv => pv.key === 'areaId')
       if (city) city.items = dealReturnObj.cityList || []
       province.value = dealReturnObj.pid || ''
-      console.log(dealReturnObj, 'dealReturnObj', province)
     }
   }
 }

+ 2 - 1
src/views/recruit/personal/remuse/components/selfEvaluation.vue

@@ -41,7 +41,8 @@ const useStore = useUserStore()
 const getData = async () => {
   await useStore.getUserBaseInfos(JSON.parse(localStorage.getItem('userInfo')).id)
   const baseInfo = JSON.parse(localStorage.getItem('baseInfo'))
-  advantage.value = baseInfo.advantage
+  // if (!baseInfo) return
+  advantage.value = baseInfo?.advantage || ''
 }
 getData()
 

+ 17 - 3
src/views/recruit/personal/shareJob/sendResume/simple.vue

@@ -5,7 +5,8 @@
     :widthType="2"
     titleClass="text-h6"
     title="补充基本信息"
-    @close="openDialog = false"
+    :closeable="props.closeable"
+    @close="openDialog = props.closeable ? false : true"
     @submit="simpleInfoSubmit"
   >
     <simpleInfoForm ref="formRef"></simpleInfoForm>
@@ -13,6 +14,7 @@
 </template>
 
 <script setup>
+import { getToken } from '@/utils/auth'
 import simpleInfoForm from '../form/simpleInfo.vue'
 import { savePersonSimpleInfo } from '@/api/recruit/personal/shareJob'
 import { useI18n } from '@/hooks/web/useI18n'; const { t } = useI18n()
@@ -20,6 +22,12 @@ import Snackbar from '@/plugins/snackbar'
 import { ref } from 'vue'
 defineOptions({name: 'shareJob-sendResume-simple'})
 const emit = defineEmits(['simpleInfoReady'])
+const props = defineProps({
+  closeable: {
+    type: Boolean,
+    default: true
+  }
+})
 
 const openDialog = ref(false) // 默认不打开弹窗,先检验simpleInfoReady
 const info = ref(null)
@@ -32,14 +40,20 @@ setTimeout(() => { if (!info.value) getUserInfoFail() }, 10000);
 
 // 查询用户基本信息
 const getUserInfoVerify = () => {
+  if (!getToken()) {
+    clearInterval(timer.value); timer.value = null
+    return
+  }
   if (info.value) {
     if (timer.value) clearInterval(timer.value); timer.value = null
     const keyArr = ['name', 'phone', 'jobStatus', 'expType', 'eduType'] // 必填人才信息
     const simpleInfoReady = Object.keys(info.value).length && keyArr.every(e => info.value[e] && info.value[e] !== 0) // 校验必填人才信息
-    if (simpleInfoReady) emit('simpleInfoReady') // 存在
-    else {
+    if (simpleInfoReady) {
+      emit('simpleInfoReady') // 存在
+    } else {
       openDialog.value = true // 不存在
       Snackbar.warning('请先完善个人基本信息')
+      localStorage.setItem('simpleCompleteDialogHaveBeenShow', true)
     }
   }
   info.value = JSON.parse(localStorage.getItem('baseInfo'))