right.vue 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. <template>
  2. <div>
  3. <div class="accountBox d-flex mb-3 radius white-bgc flex-column">
  4. <div class="resume-header ml-3 mt-2">
  5. <div class="resume-title">我的钱包</div>
  6. </div>
  7. <div class="d-flex" v-if="userAccount && Object.keys(userAccount).length">
  8. <div v-for="val in accountList" :key="val.title" class="accountItem">
  9. <v-icon color="primary">{{ val.icon }}</v-icon>
  10. <div class="ml-1">
  11. <div class="title-text">{{ (userAccount[val.key] || 0) + val.desc }}</div>
  12. <div class="tip-text">{{ val.title }}</div>
  13. </div>
  14. </div>
  15. </div>
  16. <div v-else class="text-center font-size-14 mb-3">
  17. 请先登录
  18. </div>
  19. </div>
  20. <div class="resume d-flex">
  21. <div v-for="val in resumeList" :key="val.title" class="topping white-bgc radius">
  22. <v-icon color="primary">{{ val.icon }}</v-icon>
  23. <div class="ml-1">
  24. <div class="title-text">{{ val.title }}</div>
  25. <div class="tip-text">{{ val.desc }}</div>
  26. </div>
  27. </div>
  28. </div>
  29. <div class="attachment white-bgc radius mt-3">
  30. <div>
  31. <span class="title">{{ $t('resume.attachmentResume') }}</span>
  32. <span class="upload--text cursor-pointer" @click="openFileInput">
  33. {{ $t('common.upload') }}
  34. <input
  35. type="file"
  36. ref="fileInput"
  37. accept=".pdf, .doc, .docx"
  38. style="display: none;"
  39. @change="handleUploadFile"
  40. />
  41. </span>
  42. </div>
  43. <span class="more-text">{{ $t('resume.uploadUpToFiveCopies') }}</span>
  44. <div v-if="attachmentList.length">
  45. <div class="d-flex attachment-item my-2 cursor-pointer" v-for="k in attachmentList" :key="k.id">
  46. <v-icon color="primary">mdi-file-account</v-icon>
  47. <div class="file-name ellipsis ml-2">{{ k.title }}</div>
  48. <v-icon color="primary" @click="previewFile(k.url)">mdi-eye-outline</v-icon>
  49. <v-icon class="mx-2" color="primary" @click="handleDownload(k)">mdi-download-box-outline</v-icon>
  50. <v-icon color="error" @click="handleDelete(k)">mdi-trash-can-outline</v-icon>
  51. </div>
  52. </div>
  53. <div v-else class="more-text d-flex justify-center">暂无简历,请先上传</div>
  54. </div>
  55. </div>
  56. </template>
  57. <script setup>
  58. defineOptions({ name: 'personal-center-right'})
  59. import { ref } from 'vue'
  60. import { uploadFile } from '@/api/common'
  61. import { previewFile } from '@/utils'
  62. import { getPersonResumeCv, savePersonResumeCv, deletePersonResumeCv } from '@/api/resume'
  63. import { useI18n } from '@/hooks/web/useI18n'
  64. import { useUserStore } from '@/store/user'
  65. import Snackbar from '@/plugins/snackbar'
  66. import Confirm from '@/plugins/confirm'
  67. const { t } = useI18n()
  68. const userStore = useUserStore()
  69. const accountList = [
  70. { icon: 'mdi-currency-cny', title: '账户余额', desc: '元', key: 'balance' },
  71. { icon: 'mdi-octagram-outline', title: '剩余积分', desc: '点', key: 'point' }
  72. ]
  73. let userAccount = ref(JSON.parse(localStorage.getItem('userAccount')) || {}) // 账户信息
  74. userStore.$subscribe((mutation, state) => {
  75. userAccount.value = state.userAccount || {}
  76. })
  77. const resumeList = [
  78. { icon: 'mdi-upload', title: t('resume.topResume'), desc: t('resume.increaseMoreExposure') },
  79. { icon: 'mdi-refresh', title: t('resume.refreshResume'), desc: t('resume.enhanceResumeActivity') }
  80. ]
  81. // 获取附件
  82. const attachmentList = ref([])
  83. const getList = async () => {
  84. const data = await getPersonResumeCv()
  85. attachmentList.value = data
  86. }
  87. getList()
  88. // 选择文件
  89. const fileInput = ref()
  90. const clicked = ref(false)
  91. const openFileInput = () => {
  92. if (attachmentList.value.length >= 5) return Snackbar.warning(t('resume.uploadFiveCopies'))
  93. if (clicked.value) return
  94. clicked.value = true
  95. fileInput.value.click()
  96. clicked.value = false
  97. }
  98. // 上传附件
  99. const typeList = ['pdf', 'doc', 'docx']
  100. const handleUploadFile = async (e) => {
  101. const file = e.target.files[0]
  102. const size = file.size
  103. if (size / (1024*1024) > 10) {
  104. Snackbar.warning(t('common.fileSizeExceed'))
  105. return
  106. }
  107. const arr = file.name.split('.')
  108. if (typeList.indexOf(arr[arr.length - 1]) < 0) {
  109. Snackbar.warning(t('common.fileFormatIncorrect'))
  110. return
  111. }
  112. const formData = new FormData()
  113. formData.append('file', file)
  114. const { data } = await uploadFile(formData)
  115. if (!data) return
  116. Snackbar.success(t('common.uploadSucMsg'))
  117. await savePersonResumeCv({ title: file.name, url: data })
  118. getList()
  119. }
  120. // 删除
  121. const handleDelete = ({ id }) => {
  122. Confirm(t('common.confirmTitle'), t('resume.deleteAttachment')).then(async () => {
  123. await deletePersonResumeCv(id)
  124. Snackbar.success(t('common.delMsg'))
  125. getList()
  126. })
  127. }
  128. const getBlob = (url) => {
  129. return new Promise(resolve => {
  130. const xhr = new XMLHttpRequest()
  131. xhr.open('GET', url, true)
  132. xhr.responseType = 'blob'
  133. xhr.onload = () => {
  134. if (xhr.status === 200) resolve(xhr.response)
  135. }
  136. xhr.send()
  137. })
  138. }
  139. const saveAs = (blob, filename) => {
  140. var link = document.createElement('a')
  141. link.href = window.URL.createObjectURL(blob)
  142. link.download = filename
  143. link.click()
  144. }
  145. // 下载附件
  146. const handleDownload = (k) => {
  147. getBlob(k.url).then(blob => {
  148. saveAs(blob, k.title)
  149. })
  150. }
  151. </script>
  152. <style scoped lang="scss">
  153. .radius {
  154. border-radius: 8px;
  155. }
  156. .title {
  157. font-weight: 600;
  158. font-size: 17px;
  159. }
  160. .accountBox {
  161. width: 100%;
  162. .accountItem {
  163. display: flex;
  164. align-items: center;
  165. justify-content: center;
  166. width: 50%;
  167. height: 80px;
  168. padding: 0 12px;
  169. .tip-text {
  170. font-size: 13px;
  171. color: var(--color-666);
  172. }
  173. .title-text {
  174. font-weight: 600;
  175. font-size: 18px;
  176. &:hover {
  177. color: var(--v-primary-base);
  178. }
  179. }
  180. }
  181. }
  182. .resume {
  183. width: 100%;
  184. .topping {
  185. display: flex;
  186. align-items: center;
  187. justify-content: center;
  188. width: 50%;
  189. height: 90px;
  190. padding: 12px;
  191. margin-right: 12px;
  192. cursor: pointer;
  193. &:nth-child(2n) {
  194. margin-right: 0;
  195. }
  196. .tip-text {
  197. font-size: 12px;
  198. color: var(--color-666);
  199. }
  200. .title-text {
  201. font-weight: 600;
  202. &:hover {
  203. color: var(--v-primary-base);
  204. }
  205. }
  206. }
  207. }
  208. .attachment {
  209. padding: 12px;
  210. .more-text {
  211. font-size: 12px;
  212. color: var(--color-666);
  213. margin-left: 4px;
  214. }
  215. .upload--text {
  216. float: right;
  217. color: var(--v-primary-base);
  218. font-size: 12px;
  219. }
  220. .last-update {
  221. font-size: 12px;
  222. color: var(--color-666);
  223. }
  224. .attachment-item {
  225. color: #555;
  226. font-size: 14px;
  227. .file-name {
  228. width: 230px;
  229. &:hover {
  230. color: var(--v-primary-base);
  231. }
  232. }
  233. }
  234. }
  235. </style>