infoForm.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. <template>
  2. <div style="width: 100%;">
  3. <CtForm ref="formPageRef" :items="items">
  4. <!-- 头像 -->
  5. <template #avatar="{ item }">
  6. <!-- <div class="d-flex flex-column">
  7. <div class="d-flex align-center">
  8. </div>
  9. </div> -->
  10. <div style="color: #7a7a7a; min-width: 52px;">头像:</div>
  11. <div class="avatarsBox" @mouseover="showIcon = true" @mouseleave="showIcon = false">
  12. <v-avatar class="elevation-5" size=80 :image="getUserAvatar(item.value, male)"></v-avatar>
  13. <div v-show="showIcon" @click="openFileInput" v-bind="$attrs" class="mdi mdi-camera-outline">
  14. <input
  15. type="file"
  16. ref="fileInput"
  17. accept="image/png, image/jpg, image/jpeg"
  18. style="display: none;"
  19. @change="handleUploadFile"
  20. />
  21. </div>
  22. </div>
  23. <div style="font-size: 14px; color: var(--color-999);">只支持JPG、JPEG、PNG类型的图片,大小不超过20M</div>
  24. </template>
  25. </CtForm>
  26. </div>
  27. <ImgCropper :visible="isShowCopper" :image="selectPic" :cropBoxResizable="true" @submit="handleHideCopper" :aspectRatio="1 / 1" @close="isShowCopper = false"></ImgCropper>
  28. </template>
  29. <script setup>
  30. import { getDict } from '@/hooks/web/useDictionaries'
  31. defineOptions({name: 'dialogExtend-InfoForm'})
  32. import { reactive, ref } from 'vue'
  33. import { checkEmail } from '@/utils/validate'
  34. import { enterpriseSearchByName } from '@/api/recruit/personal/resume'
  35. import { getUserAvatar } from '@/utils/avatar'
  36. import { uploadFile } from '@/api/common'
  37. import Snackbar from '@/plugins/snackbar'
  38. import { useI18n } from '@/hooks/web/useI18n'; const { t } = useI18n()
  39. const props = defineProps({
  40. option: {
  41. type: Object,
  42. default: () => {}
  43. }
  44. })
  45. const setInfo = ref(props.option?.setInfo ? props.option.setInfo : {})
  46. const formPageRef = ref()
  47. let query = reactive({})
  48. // 企业名称下拉列表
  49. let enterpriseName = null
  50. // const enterpriseNameInput = ref('')
  51. const getEnterpriseData = async (name) => {
  52. const item = items.value.options.find(e => e.key === 'enterpriseId')
  53. if (!item) return
  54. if (item.items?.length && (enterpriseName === name)) return // 防抖
  55. // item[item.itemTextName] =
  56. enterpriseName = name
  57. if (name === null || name === '') { item.items = [] }
  58. else {
  59. const data = await enterpriseSearchByName({ name })
  60. item.items = data
  61. }
  62. }
  63. // 图片裁剪
  64. const selectPic = ref('')
  65. const isShowCopper = ref(false)
  66. const male = ref('1')
  67. const showIcon = ref(false)
  68. let positionName = null
  69. const items = ref({
  70. options: [
  71. {
  72. slotName: 'avatar',
  73. key: 'avatar',
  74. value: '',
  75. flexStyle: 'align-center'
  76. },
  77. {
  78. type: 'text',
  79. key: 'name',
  80. value: '',
  81. default: null,
  82. label: '姓名 *',
  83. outlined: true,
  84. rules: [
  85. value => {
  86. if (value) return true
  87. return '请输入您的中文名'
  88. },
  89. value => {
  90. var regex = /^[\u4e00-\u9fa5]+$/
  91. if (regex.test(value)) return true
  92. return '请输入正确的中文名'
  93. }
  94. ]
  95. },
  96. {
  97. type: 'autocomplete',
  98. key: 'sex',
  99. value: '1', // '1' ? '男' : '女'
  100. default: '1',
  101. label: '性别 *',
  102. outlined: true,
  103. dictTypeName: 'menduner_sex',
  104. rules: [v => !!v || '请选择性别'],
  105. items: [],
  106. change: val => male.value = val
  107. },
  108. {
  109. type: 'phoneNumber',
  110. key: 'phone',
  111. value: '',
  112. clearable: true,
  113. label: '联系手机号 *',
  114. rules: [v => !!v || '请填写联系手机号']
  115. },
  116. {
  117. type: 'text',
  118. key: 'email',
  119. value: null,
  120. default: null,
  121. label: '常用邮箱 *',
  122. outlined: true,
  123. rules: [
  124. value => {
  125. if (value) return true
  126. return '请输入联系邮箱'
  127. },
  128. value => {
  129. if (value && !checkEmail(value)) return '请输入正确的电子邮箱'
  130. return true
  131. }
  132. ]
  133. },
  134. {
  135. type: 'datePicker',
  136. mode: 'date',
  137. labelWidth: 80,
  138. key: 'birthday',
  139. value: '1990-01-01',
  140. defaultValue: new Date(1990, 1, 1),
  141. label: '出生日期 *',
  142. disabledFutureDates: true,
  143. format: 'YYYY/MM/DD',
  144. flexStyle: 'mb-7',
  145. outlined: true,
  146. rules: [v => !!v || '请选择出生日期']
  147. },
  148. {
  149. type: 'combobox',
  150. key: 'enterpriseId',
  151. value: null,
  152. default: null,
  153. label: '任职企业名称 *(可填暂无)',
  154. outlined: true,
  155. clearable: true,
  156. canBeInputted: true, //
  157. itemTextName: 'enterpriseName',
  158. itemText: 'value',
  159. itemValue: 'key',
  160. rules: [v => !!v || '请填写任职企业名称(可填暂无)'],
  161. search: getEnterpriseData,
  162. items: []
  163. },
  164. {
  165. type: 'combobox',
  166. key: 'positionId',
  167. value: null,
  168. default: null,
  169. label: '任职职位名称 *(可填暂无)',
  170. outlined: true,
  171. clearable: true,
  172. canBeInputted: true, //
  173. itemTextName: 'positionName',
  174. itemText: 'nameCn',
  175. itemValue: 'id',
  176. dictTypeName: 'positionSecondData',
  177. rules: [v => !!v || '请填写任职职位名称(可填暂无)'],
  178. search: val => positionName = val,
  179. items: []
  180. },
  181. {
  182. type: 'autocomplete',
  183. key: 'interestedPositionList',
  184. value: null,
  185. default: null,
  186. label: '意向职位 *',
  187. outlined: true,
  188. itemText: 'nameCn',
  189. itemValue: 'id',
  190. multiple: true,
  191. dictTypeName: 'positionSecondData',
  192. rules: [v => !!v || '请选择意向职位'],
  193. items: []
  194. },
  195. {
  196. type: 'autocomplete',
  197. key: 'jobStatus',
  198. value: '',
  199. default: null,
  200. label: '求职状态 *',
  201. outlined: true,
  202. itemText: 'label',
  203. itemValue: 'value',
  204. dictTypeName: 'menduner_job_seek_status',
  205. rules: [v => !!v || '请选择求职状态'],
  206. items: []
  207. },
  208. {
  209. type: 'autocomplete',
  210. key: 'expType',
  211. value: '',
  212. default: null,
  213. label: '工作经验 *',
  214. outlined: true,
  215. itemText: 'label',
  216. itemValue: 'value',
  217. dictTypeName: 'menduner_exp_type',
  218. rules: [v => !!v || '请选择工作经验'],
  219. items: []
  220. },
  221. {
  222. type: 'autocomplete',
  223. key: 'eduType',
  224. value: '',
  225. default: null,
  226. label: '最高学历 *',
  227. outlined: true,
  228. itemText: 'label',
  229. itemValue: 'value',
  230. dictTypeName: 'menduner_education_type',
  231. rules: [v => !!v || '请选择最高学历'],
  232. items: []
  233. },
  234. // label: '学制类型 *', menduner_education_system_type
  235. ]
  236. })
  237. // 选择文件
  238. const fileInput = ref()
  239. const clicked = ref(false)
  240. const openFileInput = () => {
  241. if (clicked.value) return
  242. clicked.value = true
  243. fileInput.value.click()
  244. clicked.value = false
  245. }
  246. // 上传头像
  247. const accept = ['jpg', 'png', 'jpeg']
  248. const handleUploadFile = async (e) => {
  249. const file = e.target.files[0]
  250. if (!file) return
  251. const arr = file.name.split('.')
  252. const fileType = arr?.length ? arr[arr.length-1] : ''
  253. if (!accept.includes(fileType)) return Snackbar.warning('请上传图片格式文件')
  254. const size = file.size
  255. if (size / (1024*1024) > 20) {
  256. Snackbar.warning(t('common.fileSizeExceed'))
  257. return
  258. }
  259. const reader = new FileReader()
  260. reader.readAsDataURL(file)
  261. reader.onload = () => {
  262. selectPic.value = String(reader.result)
  263. isShowCopper.value = true
  264. }
  265. }
  266. const handleHideCopper = (data) => {
  267. isShowCopper.value = false
  268. if (data) {
  269. const { file } = data
  270. if (!file) return
  271. const formData = new FormData()
  272. formData.append('file', file)
  273. formData.append('path', 'img')
  274. uploadFile(formData).then(async ({ data }) => {
  275. if (!data) return
  276. items.value.options.find(e => e.key === 'avatar').value = data
  277. })
  278. }
  279. }
  280. // 获取字典内容
  281. const getDictData = async (dictTypeName, key) => {
  282. const item = items.value.options.find(e => e.key === key)
  283. if (item) {
  284. const apiType = dictTypeName === 'positionSecondData' ? 'positionSecondData' : 'dict'
  285. const { data } = await getDict(dictTypeName, apiType === 'dict' ? null : {}, apiType)
  286. item.items = data
  287. // console.log(dictTypeName, '字典内容', data)
  288. }
  289. }
  290. const userInfo = ref(localStorage.getItem('userInfo') ? JSON.parse(localStorage.getItem('userInfo')) : {})
  291. const baseInfo = ref(localStorage.getItem('baseInfo') ? JSON.parse(localStorage.getItem('baseInfo')) : {})
  292. items.value.options.forEach((e) => {
  293. if (e.dictTypeName) getDictData(e.dictTypeName, e.key) // 查字典set options
  294. if (baseInfo.value && baseInfo.value[e.key]) e.value = baseInfo.value[e.key] // 人才信息回显
  295. if (userInfo.value && userInfo.value[e.key]) e.value = userInfo.value[e.key] // 人才信息回显
  296. if (e.key === 'sex' && e.value === '0') e.value = e.default
  297. if (setInfo.value[e.key]) e.value = setInfo.value[e.key]
  298. })
  299. // const getName = (obj, key) => {
  300. // const item = items.value.options.find(e => e.key === key)
  301. // if (!item && !item.value) return
  302. // const select = item.items.find(e => item.value === e[item.itemValue || 'value'])
  303. // if (select) {
  304. // obj[item.itemTextName] = select[item.itemText || 'label']
  305. // }
  306. // }
  307. const dealQuery = () => {
  308. query.positionName = positionName || null
  309. if (query.positionId === positionName) delete query.positionId // 有选中id传id和name,否者只传name
  310. query.enterpriseName = enterpriseName || null
  311. if (query.enterpriseId === enterpriseName) delete query.enterpriseId // 有选中id传id和name,否者只传name
  312. //
  313. if (query.interestedPositionList?.length) {
  314. query.interestedList = query.interestedPositionList.map(e => { return {positionId: e} })
  315. }
  316. query.workExpList = [{
  317. enterpriseId: query.enterpriseId,
  318. enterpriseName: query.enterpriseName || null,
  319. positionId: query.positionId,
  320. positionName: query.positionName || null,
  321. }]
  322. }
  323. const getQuery = async () => {
  324. const { valid } = await formPageRef.value.formRef.validate()
  325. if (!valid) return false
  326. const obj = {}
  327. items.value.options.forEach(e => {
  328. if (Object.prototype.hasOwnProperty.call(e, 'data')) return obj[e.key] = e.data
  329. obj[e.key] = e.value === '' ? null : e.value
  330. })
  331. if (!obj.avatar) obj.avatar = getUserAvatar(null, obj.sex)
  332. query = Object.assign(query, obj)
  333. dealQuery()
  334. return query
  335. }
  336. defineExpose({
  337. getQuery
  338. })
  339. </script>
  340. <style scoped lang="scss">
  341. .avatarsBox {
  342. height: 80px;
  343. width: 80px;
  344. position: relative;
  345. cursor: pointer;
  346. margin: 32px;
  347. margin-right: 40px;
  348. .img {
  349. width: 100%;
  350. height: 100%;
  351. }
  352. .mdi {
  353. font-size: 42px;
  354. color: #fff;
  355. }
  356. div {
  357. position: absolute;
  358. top: 50%;
  359. left: 50%;
  360. transform: translate(-50%, -50%);
  361. border-radius: 50%;
  362. }
  363. }
  364. </style>