index.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. <template>
  2. <div class="resume-box" style="position: relative; height: 100%;">
  3. <div class="resume-header">
  4. <div class="resume-title">{{ props.analysis ? '解析简历附件生成在线简历' : '附件简历'}}</div>
  5. <v-btn variant="text" color="primary" prepend-icon="mdi-plus-box" @click="handleAdd">{{ props.analysis ? '上传新的简历' : '上传'}}</v-btn>
  6. </div>
  7. <p class="font-size-14 color-999">最多只能上传5份附件简历</p>
  8. <div v-if="attachmentList.length" class="mt-5">
  9. <div
  10. :class="['position-item', 'mx-n2', 'px-2']"
  11. v-for="(k, i) in attachmentList"
  12. :key="i"
  13. @mouseenter="k.active = true"
  14. @mouseleave="k.active = false"
  15. @click.self="checkboxClick(i, !k.choose)"
  16. >
  17. <div class="d-flex">
  18. <v-checkbox-btn v-if="props.analysis" v-model="k.choose" color="primary" class="pe-2" @update:modelValue="bool => checkboxClick(i, bool)"></v-checkbox-btn>
  19. <span @click="checkboxClick(i, !k.choose)">{{ k.title }}</span>
  20. </div>
  21. <div class="float-right" v-if="k.active">
  22. <v-btn variant="text" color="primary" prepend-icon="mdi-eye-outline" @click="previewFile(k.url)">预览</v-btn>
  23. <v-btn variant="text" color="primary" prepend-icon="mdi-arrow-down-bold-outline" @click="handleDownload(k)">下载</v-btn>
  24. <v-btn variant="text" color="primary" prepend-icon="mdi-trash-can-outline" @click="handleDelete(k)">{{ $t('common.delete') }}</v-btn>
  25. </div>
  26. </div>
  27. <div v-if="props.analysis" class="d-flex flex-column align-center mt-15">
  28. <v-btn class="buttons" color="primary" @click="handleAnalysis">开始解析</v-btn>
  29. <v-btn class="mt-2" variant="text" color="primary" to="/recruit/personal/personalCenter/resume/online">返回在线简历</v-btn>
  30. </div>
  31. </div>
  32. <div v-else class="resumeNoDataText tips">请上传您的附件简历</div>
  33. </div>
  34. <CtDialog
  35. :visible="showUploadDialog"
  36. :widthType="2"
  37. :footer="true"
  38. title="附件简历上传"
  39. titleClass="text-h6"
  40. @close="showUploadDialog = false"
  41. @submit="handleSubmit"
  42. >
  43. <CtForm ref="CtFormRef" :items="formItems">
  44. <template #uploadFile="{ item }">
  45. <TextInput v-model="item.value" :item="item" @click="openFileInput"></TextInput>
  46. <File ref="uploadFile" @success="handleUploadResume"></File>
  47. </template>
  48. </CtForm>
  49. <div class="color-666" style="font-size: 13px;">* 仅支持.doc, .docx, .pdf文件且大小不能超过20MB</div>
  50. </CtDialog>
  51. </template>
  52. <script setup>
  53. defineOptions({ name: 'person-center-resume-annex'})
  54. import { ref } from 'vue'
  55. import Snackbar from '@/plugins/snackbar'
  56. import Confirm from '@/plugins/confirm'
  57. import { useI18n } from '@/hooks/web/useI18n'
  58. import { getPersonResumeCv, deletePersonResumeCv, savePersonResumeCv } from '@/api/recruit/personal/resume'
  59. import { getBlob, saveAs, previewFile } from '@/utils'
  60. const emit = defineEmits(['analysis'])
  61. const props = defineProps({
  62. analysis: { // 简历解析
  63. type: Boolean,
  64. default: false
  65. }
  66. })
  67. const { t } = useI18n()
  68. const CtFormRef = ref()
  69. const formItems = ref({
  70. options: [
  71. {
  72. type: 'text',
  73. key: 'title',
  74. value: '',
  75. label: '附件简历名称 *',
  76. rules: [v => !!v || '请输入附件简历名称']
  77. },
  78. {
  79. slotName: 'uploadFile',
  80. key: 'url',
  81. value: '',
  82. truthValue: '',
  83. label: '点击上传附件简历 *',
  84. outline: true,
  85. accept: '.doc, .docx, .pdf',
  86. prependInnerIcon: 'mdi-file-document-outline',
  87. rules: [v => !!v || '请上传您的附件简历']
  88. }
  89. ]
  90. })
  91. // 获取附件
  92. const attachmentList = ref([])
  93. const getList = async () => {
  94. const data = await getPersonResumeCv()
  95. attachmentList.value = data
  96. }
  97. getList()
  98. // 选择文件
  99. const uploadFile = ref()
  100. const openFileInput = () => {
  101. uploadFile.value.trigger()
  102. }
  103. // 上传附件
  104. const handleUploadResume = async (url, title, filename) => {
  105. const obj = formItems.value.options.find(e => e.key === 'url')
  106. obj.value = filename
  107. obj.truthValue = url
  108. }
  109. const showUploadDialog = ref(false)
  110. const handleAdd = () => {
  111. if (attachmentList.value.length >= 5) return Snackbar.warning(t('resume.uploadFiveCopies'))
  112. formItems.value.options.forEach(e => {
  113. e.value = ''
  114. e.truthValue = ''
  115. })
  116. showUploadDialog.value = true
  117. }
  118. // 上传附件
  119. const handleSubmit = async () => {
  120. const { valid } = await CtFormRef.value.formRef.validate()
  121. if (!valid) return
  122. const obj = {}
  123. formItems.value.options.forEach(e => {
  124. obj[e.key] = e.truthValue || e.value
  125. })
  126. if (!obj.title || !obj.url) return
  127. await savePersonResumeCv(obj)
  128. getList()
  129. showUploadDialog.value = false
  130. Snackbar.success(t('common.uploadSucMsg'))
  131. }
  132. // 删除
  133. const handleDelete = ({ id }) => {
  134. Confirm(t('common.confirmTitle'), t('resume.deleteAttachment')).then(async () => {
  135. await deletePersonResumeCv(id)
  136. Snackbar.success(t('common.delMsg'))
  137. getList()
  138. })
  139. }
  140. // 下载附件
  141. const handleDownload = (k) => {
  142. getBlob(k.url).then(blob => {
  143. saveAs(blob, k.title)
  144. })
  145. }
  146. const fileUrl = ref('')
  147. const checkboxClick = (i, bool) => {
  148. if (!props.analysis) return
  149. let item = null
  150. attachmentList.value.forEach((e, index) => {
  151. e.choose = i === index ? bool : false
  152. if (e.choose) item = e
  153. })
  154. if (item?.url) fileUrl.value = encodeURIComponent(item.url)
  155. }
  156. const handleAnalysis = () => {
  157. if (!fileUrl.value) return Snackbar.warning('请选择要解析的简历附件')
  158. emit('analysis', fileUrl.value)
  159. }
  160. </script>
  161. <style scoped lang="scss">
  162. .position-item {
  163. display: flex;
  164. justify-content: space-between;
  165. cursor: pointer;
  166. border-radius: 6px;
  167. line-height: 40px;
  168. font-size: 15px;
  169. &:hover {
  170. background-color: var(--color-f8);
  171. }
  172. span {
  173. font-size: 15px;
  174. }
  175. .grey-text {
  176. color: var(--color-999);
  177. }
  178. }
  179. .tips {
  180. position: absolute;
  181. top: 50%;
  182. left: 50%;
  183. transform: translate(-50%, -50%);
  184. }
  185. </style>