index.vue 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. <template>
  2. <div>
  3. <div class="tabHeader">
  4. <div class="d-flex align-center justify-space-between">
  5. <ProgressBar :num="completeNum" :total="items?.length" style="width: 100%;"></ProgressBar>
  6. <v-btn variant="text" color="primary" @click="handleImportAttachment">导入已有简历</v-btn>
  7. </div>
  8. <v-tabs v-model="tab" align-tabs="start" color="primary" bg-color="#f7f8fa">
  9. <v-tab v-for="k in items" :key="k.path" :value="k.value" @click="handleClick(k)">
  10. {{ k.text }}
  11. <template v-slot:append>
  12. <template v-if="k.status === false">
  13. <v-icon color="orange">mdi-information-outline</v-icon>
  14. <v-tooltip activator="parent" location="top">请完善{{ k.text }}</v-tooltip>
  15. </template>
  16. </template>
  17. </v-tab>
  18. </v-tabs>
  19. </div>
  20. <div class="pt-3 contentBox px-1" id="contentBox" ref="scrollBox">
  21. <component
  22. v-for="(val, index) in paths"
  23. :key="index"
  24. :is="val"
  25. @complete="complete"
  26. />
  27. </div>
  28. </div>
  29. <Loading :visible="loading"></Loading>
  30. <selectResumeDialog v-model="showAttachment" title="请选择已有的简历导入" :list="attachmentList" @submit="handleAttachmentSubmit" @close="showAttachment = false" />
  31. <resumeAnalysis v-model="showAnalysis" :data="result" :fileUrl="encodeURIComponent(fileUrl)" @close="analysisClose" />
  32. </template>
  33. <script setup>
  34. defineOptions({ name: 'person-center-resume-online'})
  35. import { ref, onMounted } from 'vue'
  36. import { useI18n } from '@/hooks/web/useI18n'
  37. import basicInfo from './components/basicInfo.vue'
  38. import selfEvaluation from './components/selfEvaluation.vue'
  39. import jobIntention from './components/jobIntention.vue'
  40. import trainingExperience from './components/trainingExperience.vue'
  41. import educationExp from './components/educationExp.vue'
  42. import workExperience from './components/workExperience.vue'
  43. // import projectExperience from './components/projectExperience.vue'
  44. import vocationalSkills from './components/vocationalSkills.vue'
  45. import { resumePersonFillAll, getPersonResumeCv, resumeParser2 } from '@/api/recruit/personal/resume'
  46. import Snackbar from '@/plugins/snackbar'
  47. import { useRouter } from 'vue-router'
  48. import selectResumeDialog from '@/views/recruit/personal/position/components/jobDetails/selectResumeDialog.vue'
  49. import resumeAnalysis from './resumeAnalysis.vue'
  50. import { Base64 } from 'js-base64'
  51. // import { dataObj } from './test.js'
  52. const router = useRouter()
  53. const { t } = useI18n()
  54. const scrollBox = ref()
  55. const tab = ref(0)
  56. const loading = ref(false)
  57. const paths = [basicInfo, selfEvaluation, jobIntention, educationExp, workExperience, vocationalSkills, trainingExperience]
  58. const items = ref([
  59. { text: t('resume.basicInfo'), id: 'basicInfo', status: false },
  60. { text: t('resume.personalAdvantages'), id: 'selfEvaluation', status: false },
  61. { text: t('resume.jobIntention'), id: 'jobIntention', status: false },
  62. { text: t('resume.educationExp'), id: 'educationExp', status: false },
  63. { text: t('resume.workExperience'), id: 'workExperience', status: false },
  64. // { text: t('resume.projectExperience'), id: 'projectExperience', status: false },
  65. { text: t('resume.vocationalSkills'), id: 'vocationalSkills', status: false },
  66. { text: t('resume.trainingExperience'), id: 'trainingExperience', status: false },
  67. ])
  68. onMounted(() => {
  69. const selector = document.getElementById('contentBox')
  70. if (!selector) return
  71. selector.style.top = `${scrollBox.value.offsetTop + 12}px`
  72. })
  73. const handleClick = (item) => {
  74. if (item.id) {
  75. const selector = document.getElementById(item.id)
  76. if (!selector) {
  77. scrollBox.value.scrollTo({
  78. top: 0,
  79. behavior: 'smooth'
  80. })
  81. return
  82. }
  83. scrollBox.value.scrollTo({
  84. top: selector.offsetTop - scrollBox.value.offsetTop - 12,
  85. behavior: 'smooth'
  86. })
  87. }
  88. }
  89. // 简历完成度
  90. const completeNum = ref(0)
  91. const complete = (val) => {
  92. if (!val?.id) return
  93. completeNum.value = 0
  94. const data = items.value.find(e => e.id === val.id)
  95. data.status = val.status || false
  96. items.value.forEach(e => {
  97. if (e.status) completeNum.value++
  98. })
  99. // 首次简历填写完成百分百加积分
  100. const allCompleted = items.value.every(e => e.status)
  101. if (allCompleted) {
  102. resumePersonFillAll()
  103. }
  104. }
  105. // 获取附件
  106. const attachmentList = ref([])
  107. const getAttachmentList = async () => {
  108. const data = await getPersonResumeCv()
  109. attachmentList.value = data
  110. }
  111. getAttachmentList()
  112. // 导入附件简历
  113. const showAttachment = ref(false)
  114. const handleImportAttachment = () => {
  115. if (!attachmentList.value.length) {
  116. Snackbar.warning('请先上传附件简历')
  117. router.push('/recruit/personal/personalCenter/resume/attachment')
  118. return
  119. }
  120. showAnalysis.value = false
  121. showAttachment.value = true
  122. }
  123. // const result = ref(JSON.parse(JSON.stringify(dataObj)))
  124. const result = ref({})
  125. const fileUrl = ref('')
  126. const showAnalysis = ref(false)
  127. const handleAttachmentSubmit = async (val) => {
  128. if (!val) return
  129. const url = attachmentList.value.find(e => e.id === val).url
  130. const baseUrl = import.meta.env.VITE_PREVIEW_URL
  131. fileUrl.value = !url.includes('.pdf') ? `${baseUrl}/onlinePreview?url=${encodeURIComponent(Base64.encode(url))}` : url
  132. showAttachment.value = false
  133. loading.value = true
  134. try {
  135. const data = await resumeParser2({ fileUrl: fileUrl.value })
  136. result.value = data || {}
  137. showAnalysis.value = true
  138. } catch (error) {
  139. console.log(error)
  140. } finally {
  141. loading.value = false
  142. }
  143. }
  144. const analysisClose = () => {
  145. result.value = {}
  146. showAnalysis.value = false
  147. }
  148. </script>
  149. <style scoped lang="scss">
  150. .tabHeader {
  151. position: sticky;
  152. }
  153. .contentBox {
  154. height: calc(100vh - 251px);
  155. overflow: auto;
  156. }
  157. ::-webkit-scrollbar {
  158. width: 8px;
  159. height: 10px;
  160. }
  161. ::-webkit-scrollbar-thumb, .temporaryAdd ::-webkit-scrollbar-thumb, .details_edit ::-webkit-scrollbar-thumb {
  162. // 滚动条-颜色
  163. background: #c3c3c379;
  164. border-radius: 10px;
  165. }
  166. ::-webkit-scrollbar-track, .temporaryAdd ::-webkit-scrollbar-track, .details_edit ::-webkit-scrollbar-track {
  167. // 滚动条-底色
  168. background: #e5e5e58f;
  169. border-radius: 10px;
  170. }
  171. </style>