Explorar o código

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

Xiao_123 hai 6 meses
pai
achega
c0f53e22e0

+ 8 - 0
src/api/common/index.js

@@ -303,6 +303,14 @@ export const getMessageType = async (data) => {
   })
 }
 
+// 聊天记录撤回
+export const messageBack = async (data) => {
+  return await request.post({
+    url: '/app-api/im/im/messages/back',
+    data
+  })
+}
+
 
 // 求职端-根据邀请人id获取面试邀约列表
 export const getInterviewInviteListByInviteUserId = async (inviteUserId) => {

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

@@ -116,7 +116,7 @@ watch(() => props.visible, (newVal) => {
 })
 
 const dialogWidth = computed(() => {
-  const arr = ['900px', '1200px', '400px', '600px', '500px']
+  const arr = ['900px', '1200px', '400px', '600px', '500px', '96%']
   return arr[+props.widthType]
 })
 const handleClose = () => {

+ 1 - 1
src/config/axios/service.js

@@ -203,7 +203,7 @@ service.interceptors.response.use(
       Snackbar.error(t('sys.api.errMsg901'))
       return Promise.reject(new Error(msg))
     } else if (code !== 200) {
-      if (code === 1100017019 || code === 1100016002 || code === 1100021016) {
+      if ([1100017019, 1100016002, 1100021016, 1100017022].includes(code)) {
         // 1100017019邮箱未注册、1100016002手机号未注册过、1100021016注册企业申请中
         // 未注册过的手机号将code码返回
         return Promise.reject(data)

+ 6 - 2
src/views/login/forgotPassword.vue

@@ -3,7 +3,9 @@
     <navBar v-if="!isMobile" :showLoginBtn="false" class="navBar"></navBar>
     <div class="content pa-10">
       <div class="resume-header">
-        <div class="resume-title">修改密码</div>
+        <div class="resume-title">
+          {{ route.query.forgot ? '密码不安全,请重置密码' : '修改密码' }}
+        </div>
       </div>
       <editPasswordPage class="mt-5" :showCancelBtn="false" :isReset="true" @cancel="router.push('/login')"></editPasswordPage>
     </div>
@@ -12,12 +14,14 @@
 
 <script setup>
 defineOptions({ name: 'forgotPassword'})
-import { useRouter } from 'vue-router'
+import { useRouter, useRoute } from 'vue-router'
 import navBar from '@/layout/personal/navBar.vue'
 import editPasswordPage from '@/views/login/components/editPassword.vue'
 import { ref, onMounted } from 'vue'
 
 const router = useRouter()
+const route = useRoute()
+
 
 const isMobile = ref(false)
 onMounted(() => {

+ 6 - 0
src/views/login/index.vue

@@ -162,8 +162,14 @@ const handleLogin = async () => {
     router.push({ path: '/recruitHome' })
 
   } catch (err) {
+    console.log(err)
     if (!err.code) return
     // 企业注册申请中
+    if (err.code === 1100017022) {
+      // 密码不安全
+      router.push('/forgotPassword?forgot=1')
+      return
+    }
     if (err.code === 1100021016) {
       handleCheckEnterprise()
       return

+ 15 - 3
src/views/recruit/components/message/components/chatting.vue

@@ -59,7 +59,7 @@
         <div v-for="(val, i) in items" :key="i" :id="val.id">
           <div class="time-box">{{ timesTampChange(+(val.timestamp.padEnd(13, '0'))) }}</div>
           
-          <!-- <template v-if="val.payload?.type === 102 && val.from_uid !== IM.uid"> -->
+          
           <template v-if="val.payload?.type === 102">
             <v-card
               color="teal"
@@ -99,7 +99,10 @@
               </div>
             </v-card>
           </template>
-          <div :class="['message-view_item', val.from_uid === IM.uid ? 'is-self' : 'is-other']">
+          <template v-if="val.payload?.type === 1006">
+            <div class="text-subtitle-2 text-center text-grey">{{ val.from_uid === IM.uid ? '' : '对方' }}撤回了一份简历</div>
+          </template>
+          <div v-if="val.payload?.type !== 1006" :class="['message-view_item', val.from_uid === IM.uid ? 'is-self' : 'is-other']">
             <div style="width: 40px; height: 40px;">
               <v-avatar>
                 <v-img
@@ -147,7 +150,10 @@
                 </v-card-text>
                 <v-card-actions class="justify-center">
                   <!-- <v-btn variant="tonal" flat size="small" color="error" @click="handleRejectReceive(val.payload)">拒绝</v-btn> -->
-                  <v-btn v-if="val.payload.content?.type === 1" block  variant="tonal" flat size="small" color="success" @click="handlePreview(val.payload)">点击预览附件简历</v-btn>
+                  <template v-if="val.payload.content?.type === 1">
+                    <v-btn block variant="tonal" flat size="small" color="success" @click="handlePreview(val.payload)">点击预览附件简历</v-btn>
+                    <!-- <v-btn variant="tonal" flat size="small" color="error" @click="handleBack(val)">撤回简历</v-btn> -->
+                  </template>
                   <v-btn v-if="val.payload.content?.type === 2" block  variant="tonal" flat size="small" color="success" @click="handleSendResume(val.payload)">点击发送附件简历</v-btn>
                 </v-card-actions>
               </v-card>
@@ -386,6 +392,11 @@ const handleToCenter = () => {
 const handlePreview = (val) => {
   emits('handlePreview', val)
 }
+
+const handleBack = (val) => {
+  emits('handleBack', val)
+}
+
 // 发送简历
 const handleSendResume = (val) => {
   emits('handleSendResume', val)
@@ -400,6 +411,7 @@ const handleSendResume = (val) => {
 // }
 
 defineExpose({
+  chatRef,
   reset,
   changeLoading,
   scrollBottom,

+ 30 - 3
src/views/recruit/components/message/index.vue

@@ -97,6 +97,7 @@
         @handleRefuse="handleRefuse"
         @handlePreview="handlePreview"
         @handleSendResume="handleSendResume"
+        @handleBack="handleBack"
       >
         <template #tools>
           <v-btn
@@ -180,7 +181,7 @@ import Chatting from './components/chatting.vue'
 import { initConnect, send, initChart, getMoreMessages, checkConversation } from '@/hooks/web/useIM'
 import { useI18n } from '@/hooks/web/useI18n'
 import { getPositionDetails, jobCvRelCheckSend, jobCvRelSend, jobCvRelHireSend } from '@/api/position'
-import { getInterviewInviteListByInviteUserId, getMessageType } from '@/api/common'
+import { getInterviewInviteListByInviteUserId, getMessageType, messageBack } from '@/api/common'
 // import { getUserInfo } from '@/api/personal/user'
 import { getBaseInfo } from '@/api/common'
 import { getJobAdvertised } from '@/api/enterprise'
@@ -205,7 +206,7 @@ const chatRef = ref()
 
 const IM = useIMStore()
 // 自己的信息
-const { entBaseInfo } = useUserStore()
+const { entBaseInfo, baseInfo } = useUserStore()
 
 const isEnterprise = inject('isEnterprise')
 // 实例
@@ -415,6 +416,7 @@ async function getMessageTypeSync () {
   handleChangeSendResumeStatus(check)
 }
 
+// 修改发送状态
 function handleChangeSendResumeStatus (status) {
   if (!isEnterprise) {
     const item = userTools.value.find(e => e.key === 'sendResume')
@@ -493,6 +495,23 @@ async function handleSendResume (item) {
   }
 }
 
+// 撤回简历
+async function handleBack (val) {
+  console.log(val)
+  try {
+    // 撤回简历
+    // 撤回聊天
+    // await messageBack({
+    //   channelId: val.channel_id,
+    //   messageId: val.message_id,
+    //   nickName: baseInfo.name
+    //   // enterpriseId: ''
+    // })
+  } catch (error) {
+    console.log(error)
+  }
+}
+
 /**
  * 发送简历
  * text param
@@ -594,13 +613,21 @@ const handlePreview = (val) => {
 }
 
 const handleGetMore = async () => {
+  // 当前滚动条高度
+  const scrollHeight = chatRef.value.chatRef.scrollHeight
+  // 当前滚动条距离
+  const scrollTop = chatRef.value.chatRef.scrollTop
   try {
     chatRef.value.changeLoading(true)
     pageSize.value++
     const { list, more } = await getMoreMessages(pageSize.value, channelItem.value)
     messageItems.value.unshift(...list.value)
     hasMore.value = more
-    // chatRef.value.scrollBottom()
+    nextTick(() => {
+      // 渲染后高度
+      const _scrollHeight = chatRef.value.chatRef.scrollHeight
+      chatRef.value.chatRef.scrollTop = _scrollHeight - scrollHeight - scrollTop
+    })
   } finally {
     chatRef.value.changeLoading(false)
   }

+ 24 - 5
src/views/recruit/entRegister/register.vue

@@ -15,6 +15,18 @@
         <!-- 标题 -->
         <div class="mb-10" :class="isMobile ? 'mt-0': 'mt-n8'" style="font-size: 22px; font-weight: bold; text-align: center;">{{ $t('enterprise.registeringNewEnterprise') }}</div>
         <CtForm ref="CtFormRef" :items="formItems" style="width: 100%;">
+          <template #nameTip>
+            <div style="color:var(--color-999); font-size: 13px;" class="mt-2">
+              <span class="mr-1">注:</span>
+              <span>需与营业执照一致,上传营业执照可自动识别</span>
+            </div>
+          </template>
+          <template #codeTip>
+            <div style="color:var(--color-999); font-size: 13px;" class="mt-2">
+              <span class="mr-1">注:</span>
+              <span>需与营业执照一致,上传营业执照可自动识别</span>
+            </div>
+          </template>
           <template #businessLicense>
             <!-- 上传照片 -->
             <div class="d-flex flex-column mb-6">
@@ -62,7 +74,7 @@
 
     <Loading :visible="loading"></Loading>
     
-    <CtDialog :visible="showContactList" title="添加联系人" :footer="true" widthType="3" @close="showContactList = false" @submit="contactSubmit">
+    <CtDialog :visible="showContactList" title="添加联系人" :footer="true" :widthType="isMobile ? 5 : 3" @close="showContactList = false" @submit="contactSubmit">
       <div style="min-height: 50vh;">
         <div>
           <div v-for="(item, index) in contactCopy" :key="index" class="contactItemCard">
@@ -72,7 +84,7 @@
               <div v-else style="font-size: 12px; color: #999">不可删除</div>
             </div>
             <TextUI v-model="item.contactName" :item="{...contactNameObj}"></TextUI>
-            <TextUI v-model="item.phone" :item="{...phoneObj, disabled: (!index&&bossPhone !== '')}"></TextUI>
+            <TextUI v-model="item.phone" :item="{...phoneObj, disabled: index === 0 && bossPhone !== ''}" ></TextUI>
             <TextUI v-model="item.email" :item="{...emailObj}"></TextUI>
             <TextUI v-model="item.password" :item="{...passwordObj}"></TextUI>
             <TextUI v-model="item.passwordConfirm" :item="{...passwordConfirmObj}"></TextUI>
@@ -145,7 +157,7 @@ const saveRegisterInfo = () => {
   localStorage.setItem('enterpriseRegisterInfo', JSON.stringify(obj))
 }
 
-const codeLabelTet = '企业统一社会信用代码(可从上传的营业执照自动识别)'
+const codeLabelTet = '企业统一社会信用代码'
 // 是否筹建中
 const isPrepareChange = () => {
   const code = formItems.value.options.find(e => e.key === 'code')
@@ -173,11 +185,14 @@ const formItems = ref({
     {
       slotName: 'businessLicense'
     },
+    {
+      slotName: 'nameTip'
+    },
     {
       type: 'text',
       key: 'name',
       value: '',
-      label: '企业名称(需要与营业执照完全一致,可从上传的营业执照自动识别)*',
+      label: '企业名称 *',
       counter: 50,
       rules: [v => !!v || '请输入企业名称'],
       blur: saveRegisterInfo
@@ -190,6 +205,9 @@ const formItems = ref({
       counter: 50,
       blur: saveRegisterInfo
     },
+    {
+      slotName: 'codeTip'
+    },
     {
       type: 'text',
       key: 'code',
@@ -283,7 +301,8 @@ const handleCommit = async () => {
   localStorage.removeItem('loginAccount')
   localStorage.removeItem('enterpriseRegisterInfo')
   Snackbar.success(t('common.submittedSuccessfully'))
-  router.push({ path: '/recruit/entRegister/inReview' })
+  if (isMobile.value) router.push({ path: '/recruit/enterprise/talentRecommendation' })
+  else router.push({ path: '/recruit/entRegister/inReview' })
 }
 
 // 不通过的企业注册申请 重新发起

+ 131 - 2
src/views/recruit/enterprise/entInfoSetting/informationSettingsComponents/businessInformation.vue

@@ -1,7 +1,32 @@
 <!-- 工商信息 -->
 <template>
-  <div>
-    <CtForm ref="CtFormRef" class="mt-3" :items="formItems" style="width: 900px;margin: 0 auto"></CtForm>
+  <div style="width: 900px;margin: 0 auto">
+    <CtForm ref="CtFormRef" class="mt-3" :items="formItems">
+      <template #businessLicense>
+        <!-- 上传照片 -->
+        <div class="d-flex flex-column mb-6">
+          <div style="color: var(--color-999);">
+            <!-- <span class="mr-1" style="color: var(--v-error-base);">*</span> -->
+            <span>上传营业执照</span>
+            <span>支持jpg、jpeg、png格式,图片大小不得超过10M</span>
+          </div>
+          <div class="file-box">
+            <Img 
+              class="mt-3" 
+              tips="上传图片" 
+              :value="licenseUrl"
+              :showSnackbar="false" 
+              @imgClick="showPreview = !showPreview" 
+              :showCursor="true" 
+              @success="handleUploadImg" 
+              @delete="handleDeleteImg"
+            ></Img>
+          </div>
+        </div>
+      </template>
+    </CtForm>
+    <Loading :visible="loading"></Loading>
+    <PreviewImg v-if="showPreview" :current="current" :list="[licenseUrl]" @close="showPreview = !showPreview"></PreviewImg>
     <div class="text-center">
       <v-btn color="primary" class="buttons mt-3 mb-10" @click="handleSave">{{ $t('common.save') }}</v-btn>
     </div>
@@ -13,12 +38,26 @@ import CtForm from '@/components/CtForm'
 import { getEnterpriseBusiness, updateEnterpriseBusiness } from '@/api/enterprise'
 import Snackbar from '@/plugins/snackbar'
 import { reactive, ref } from 'vue'
+import { useI18n } from '@/hooks/web/useI18n'
+import { getBusinessLicenseOCR } from '@/api/common'
+import Confirm from '@/plugins/confirm'
 // import { getDict } from '@/hooks/web/useDictionaries'
 const emit = defineEmits(['complete'])
 
 defineOptions({name: 'informationSettingsComponents-businessInformation'})
+const { t } = useI18n()
+const showPreview = ref(false)
+const current = ref(0)
+let licenseUrl = ref('')
+// 图片预览
+const loading = ref(false)
+
 const formItems = ref({
   options: [
+    {
+      slotName: 'businessLicense',
+      noParam: true,
+    },
     {
       type: 'text',
       key: 'name',
@@ -145,8 +184,16 @@ const formItems = ref({
   ]
 })
 
+const business = ref({})
 const CtFormRef = ref()
 const query = reactive({})
+
+// const info = JSON.parse(localStorage.getItem('userApplyInfo'))
+
+// licenseUrl.value = info?.businessLicenseUrl ?? ''
+
+
+
 const handleSave = async () => {
   const { valid } = await CtFormRef.value.formRef.validate()
   if (!valid) return
@@ -154,6 +201,7 @@ const handleSave = async () => {
     if (e.noParam) return
     query[e.key] = e.value
   })
+  query.businessUrl = licenseUrl.value
   if (!query.establishmentTime) return Snackbar.warning('请选择成立时间')
   await updateEnterpriseBusiness(query)
   Snackbar.success('编辑成功')
@@ -166,6 +214,53 @@ const completeFun = (completeCount = 0) => {
   emit('complete', { totalCount, completeCount, id: 'businessInformation' })
 }
 
+// 识别营业执照图片
+const getOcr = async () => {
+  loading.value = true
+  try {
+    const data = await getBusinessLicenseOCR(licenseUrl.value)
+    
+    if (data && Object.keys(data).length) {
+      Confirm(t('common.confirmTitle'), '是否根据营业执照内容替换以下相关信息').then(() => {
+        formItems.value.options.forEach(e => {
+          if (e.noParam) return
+          // if (e.key in data) {
+          if (data[e.key]) {
+            e.value = data[e.key]
+          }
+        })
+        business.value = data
+      })
+      
+    } else {
+      licenseUrl.value = ''
+      Confirm(t('common.confirmTitle'), '营业执照图片识别失败,请重新上传清晰合法图片', { hideCancelBtn: true })
+    }
+  } catch (error) {
+    licenseUrl.value = ''
+    Confirm(t('common.confirmTitle'), error, { hideCancelBtn: true })
+  } finally {
+    loading.value = false
+  }
+}
+
+// 上传
+const handleUploadImg = (url) => {
+  licenseUrl.value = url
+  if (licenseUrl.value) {
+    getOcr()
+  }
+}
+
+const handleDeleteImg = () => {
+  licenseUrl.value = ''
+  // business.value = {}
+//   formItems.value.options.find(e => e.key === 'code').value = ''
+//   formItems.value.options.find(e => e.key === 'name').value = ''
+//   saveRegisterInfo()
+}
+
+
 // 获取基本信息
 const getBaseInfo = async () => {
   let completeCount = 0
@@ -175,10 +270,12 @@ const getBaseInfo = async () => {
     if (!data) return
     query.id = data.id
     formItems.value.options.forEach(item => {
+      if (item.noParam) return
       item.value = data[item.key]
       // 完成度展示
       if (!item.rules || (item.value !== undefined && item.value !== null && item.value !== '')) completeCount++
     })
+    licenseUrl.value = data.businessUrl
     // 完成度展示
     completeFun(completeCount)
   } catch (error) {
@@ -188,4 +285,36 @@ const getBaseInfo = async () => {
 getBaseInfo()
 </script>
 <style lang="scss" scoped>
+.file-box {
+  display: flex;
+  flex-wrap: wrap; /* 允许换行 */
+  width: 100%; /* 设置容器宽度 */
+  .file-item {
+    height: 80px;
+    width: 100px;
+    border-radius: 5px;
+    margin-right: 8px;
+    margin-top: 12px;
+    // border: 1px solid rgb(188, 188, 188);
+    border: 1px solid rgba(188, 188, 188, 0.5);
+  }
+  .file-input-box {
+    position: relative;
+    border: 1px solid rgb(188, 188, 188);
+    cursor: pointer;
+    .icon {
+      position: absolute;
+      top: 45%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      color: var(--color-999);
+    }
+  }
+  // 验证是否为空
+  .verifyAct {
+    color: var(--v-error-base);
+    border: 1px solid var(--v-error-base);
+    .icon { color: var(--v-error-base); }
+  }
+}
 </style>