studentInfoForm.vue 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. <template>
  2. <div style="width: 100%;">
  3. <CtForm ref="formPageRef" :items="items">
  4. <!-- 头像 -->
  5. <template #avatar="{ item }">
  6. <div style="color: #7a7a7a; min-width: 52px;">头像:</div>
  7. <div class="avatarsBox" @mouseover="showIcon = true" @mouseleave="showIcon = false">
  8. <v-avatar class="elevation-5" size=80 :image="getUserAvatar(item.value, male)"></v-avatar>
  9. <div v-show="showIcon" @click="openFileInput" v-bind="$attrs" class="mdi mdi-camera-outline">
  10. <input
  11. type="file"
  12. ref="fileInput"
  13. accept="image/png, image/jpg, image/jpeg"
  14. style="display: none;"
  15. @change="handleUploadFile"
  16. />
  17. </div>
  18. </div>
  19. <div style="font-size: 14px; color: var(--color-999);">只支持JPG、JPEG、PNG类型的图片,大小不超过20M</div>
  20. </template>
  21. </CtForm>
  22. </div>
  23. <ImgCropper :visible="isShowCopper" :image="selectPic" :cropBoxResizable="true" @submit="handleHideCopper" :aspectRatio="1 / 1" @close="isShowCopper = false"></ImgCropper>
  24. </template>
  25. <script setup>
  26. import { getDict } from '@/hooks/web/useDictionaries'
  27. defineOptions({name: 'dialogExtend-InfoForm'})
  28. import { reactive, ref } from 'vue'
  29. import { schoolList, departmentList } from '@/api/recruit/personal/resume'
  30. import { getUserAvatar } from '@/utils/avatar'
  31. import { uploadFile } from '@/api/common'
  32. import Snackbar from '@/plugins/snackbar'
  33. import { useI18n } from '@/hooks/web/useI18n'; const { t } = useI18n()
  34. import { isValidIdCard18 } from '@/utils/validate'
  35. const props = defineProps({
  36. option: {
  37. type: Object,
  38. default: () => {}
  39. }
  40. })
  41. const setInfo = ref(props.option?.setInfo ? props.option.setInfo : {})
  42. const formPageRef = ref()
  43. let query = reactive({})
  44. // 图片裁剪
  45. const selectPic = ref('')
  46. const isShowCopper = ref(false)
  47. const male = ref('1')
  48. const showIcon = ref(false)
  49. const items = ref({
  50. options: [
  51. {
  52. slotName: 'avatar',
  53. key: 'avatar',
  54. value: '',
  55. flexStyle: 'align-center mb-3'
  56. },
  57. {
  58. type: 'text',
  59. key: 'name',
  60. value: '',
  61. default: null,
  62. label: '姓名 *',
  63. outlined: true,
  64. rules: [
  65. value => {
  66. if (value) return true
  67. return '请输入您的中文名'
  68. },
  69. value => {
  70. var regex = /^[\u4e00-\u9fa5]+$/
  71. if (regex.test(value)) return true
  72. return '请输入正确的中文名'
  73. }
  74. ]
  75. },
  76. {
  77. type: 'autocomplete',
  78. key: 'sex',
  79. value: '1', // '1' ? '男' : '女'
  80. default: '1',
  81. label: '性别 *',
  82. outlined: true,
  83. dictTypeName: 'menduner_sex',
  84. rules: [v => !!v || '请选择性别'],
  85. items: [],
  86. change: val => male.value = val
  87. },
  88. {
  89. type: 'text',
  90. key: 'idCardNo',
  91. value: '',
  92. label: '身份证号码 *',
  93. rules: [
  94. value => {
  95. if (!value) {
  96. return '请输入您的身份证号码'
  97. }
  98. return true
  99. },
  100. value => {
  101. if (!isValidIdCard18(value)) {
  102. return '请输入正确的身份证号码'
  103. }
  104. return true
  105. }
  106. ]
  107. },
  108. {
  109. type: 'phoneNumber',
  110. key: 'phone',
  111. value: '',
  112. clearable: true,
  113. label: '联系手机号 *',
  114. rules: [v => !!v || '请填写联系手机号']
  115. },
  116. {
  117. type: 'datePicker',
  118. mode: 'date',
  119. labelWidth: 80,
  120. key: 'birthday',
  121. value: new Date(1990, 0, 1).getTime(), // 月份是从 0 开始的
  122. label: '出生日期 *',
  123. disabledFutureDates: true,
  124. format: 'YYYY/MM/DD',
  125. flexStyle: 'mb-7',
  126. outlined: true,
  127. rules: [v => !!v || '请选择出生日期']
  128. },
  129. {
  130. type: 'autocomplete',
  131. key: 'schoolId',
  132. value: null,
  133. default: null,
  134. label: '就读学校 *',
  135. outlined: true,
  136. itemText: 'schoolName',
  137. itemValue: 'schoolId',
  138. rules: [v => !!v || '请选择就读学校'],
  139. items: [],
  140. change: e => getDepartmentList(e),
  141. },
  142. {
  143. type: 'autocomplete',
  144. key: 'schoolDepartmentName',
  145. value: null,
  146. default: null,
  147. label: '所在院系 *',
  148. outlined: true,
  149. itemText: 'departmentTitle',
  150. itemValue: 'schoolDepartmentId',
  151. rules: [v => !!v || '请选择所在院系'],
  152. items: []
  153. },
  154. {
  155. type: 'text',
  156. key: 'majorName',
  157. value: '',
  158. default: null,
  159. label: '所学专业 *',
  160. outlined: true,
  161. rules: [v => !!v || '请输入所学专业']
  162. },
  163. {
  164. type: 'text',
  165. key: 'schoolClassName',
  166. value: '',
  167. default: null,
  168. label: '所在班级 *',
  169. outlined: true,
  170. rules: [v => !!v || '请填写所在班级']
  171. },
  172. {
  173. type: 'text',
  174. key: 'studentNo',
  175. value: '',
  176. default: null,
  177. label: '学号 *',
  178. outlined: true,
  179. rules: [v => !!v || '请填写学号']
  180. },
  181. {
  182. type: 'text',
  183. key: 'emergencyContactName',
  184. value: '',
  185. default: null,
  186. label: '紧急联系人姓名 *',
  187. outlined: true,
  188. rules: [v => !!v || '请填写紧急联系人姓名']
  189. },
  190. {
  191. type: 'phoneNumber',
  192. key: 'emergencyContactPhone',
  193. value: '',
  194. clearable: true,
  195. label: '紧急联系人手机号 *',
  196. rules: [v => !!v || '请填写紧急联系人手机号']
  197. },
  198. ]
  199. })
  200. if (import.meta.env.VITE_NODE_ENV === 'production') {
  201. items.value.options = items.value.options.filter(e => e.slotName !== 'analysis')
  202. }
  203. // // 学校下拉列表
  204. const getSchoolListData = async () => {
  205. const item = items.value.options.find(e => e.key === 'schoolId')
  206. if (!item) return
  207. const { records } = await schoolList({current: 1,size: 9999})
  208. item.items = records || []
  209. }
  210. getSchoolListData()
  211. const getDepartmentList = async (e) => {
  212. const item = items.value.options.find(e => e.key === 'schoolDepartmentName')
  213. if (!item) return
  214. const query = {
  215. page: { size: 9999, current: 1 },
  216. entity: { schoolId: e }
  217. }
  218. const res = await departmentList(query)
  219. const list = res?.records?.length ? res.records : []
  220. item.items = list.map(e => e.entity)
  221. }
  222. // 选择文件
  223. const fileInput = ref()
  224. const clicked = ref(false)
  225. const openFileInput = () => {
  226. if (clicked.value) return
  227. clicked.value = true
  228. fileInput.value.click()
  229. clicked.value = false
  230. }
  231. // 上传头像
  232. const accept = ['jpg', 'png', 'jpeg']
  233. const handleUploadFile = async (e) => {
  234. const file = e.target.files[0]
  235. if (!file) return
  236. const arr = file.name.split('.')
  237. const fileType = arr?.length ? arr[arr.length-1] : ''
  238. if (!accept.includes(fileType)) return Snackbar.warning('请上传图片格式文件')
  239. const size = file.size
  240. if (size / (1024*1024) > 20) {
  241. Snackbar.warning(t('common.fileSizeExceed'))
  242. return
  243. }
  244. const reader = new FileReader()
  245. reader.readAsDataURL(file)
  246. reader.onload = () => {
  247. selectPic.value = String(reader.result)
  248. isShowCopper.value = true
  249. }
  250. }
  251. const handleHideCopper = (data) => {
  252. isShowCopper.value = false
  253. if (data) {
  254. const { file } = data
  255. if (!file) return
  256. const formData = new FormData()
  257. formData.append('file', file)
  258. formData.append('path', 'img')
  259. uploadFile(formData).then(async ({ data }) => {
  260. if (!data) return
  261. items.value.options.find(e => e.key === 'avatar').value = data
  262. })
  263. }
  264. }
  265. // 获取字典内容
  266. const getDictData = async (dictTypeName, key) => {
  267. const item = items.value.options.find(e => e.key === key)
  268. if (item) {
  269. const apiType = dictTypeName === 'positionSecondData' ? 'positionSecondData' : 'dict'
  270. const { data } = await getDict(dictTypeName, apiType === 'dict' ? null : {}, apiType)
  271. item.items = data
  272. }
  273. }
  274. const userInfo = ref(localStorage.getItem('userInfo') ? JSON.parse(localStorage.getItem('userInfo')) : {})
  275. const baseInfo = ref(localStorage.getItem('baseInfo') ? JSON.parse(localStorage.getItem('baseInfo')) : {})
  276. items.value.options.forEach((e) => {
  277. if (e.dictTypeName) getDictData(e.dictTypeName, e.key) // 查字典set options
  278. if (userInfo.value && userInfo.value[e.key]) e.value = userInfo.value[e.key] // 人才信息回显
  279. if (baseInfo.value && baseInfo.value[e.key]) e.value = baseInfo.value[e.key] // 人才信息回显
  280. if (e.key === 'sex' && e.value === '0') e.value = e.default
  281. if (setInfo.value[e.key]) e.value = setInfo.value[e.key]
  282. })
  283. const getQuery = async () => {
  284. const { valid } = await formPageRef.value.formRef.validate()
  285. if (!valid) {
  286. Snackbar.warning('请填写完整个人信息!')
  287. return false
  288. }
  289. const obj = {}
  290. items.value.options.forEach(e => {
  291. if (Object.prototype.hasOwnProperty.call(e, 'data')) return obj[e.key] = e.data
  292. obj[e.key] = e.value === '' ? null : e.value
  293. })
  294. if (!obj.avatar) obj.avatar = getUserAvatar(null, obj.sex)
  295. query = Object.assign(query, obj)
  296. return query
  297. }
  298. defineExpose({
  299. getQuery
  300. })
  301. </script>
  302. <style scoped lang="scss">
  303. .avatarsBox {
  304. height: 80px;
  305. width: 80px;
  306. position: relative;
  307. cursor: pointer;
  308. // margin: 32px;
  309. // margin-right: 40px;
  310. margin: 0 40px 0 32px;
  311. .img {
  312. width: 100%;
  313. height: 100%;
  314. }
  315. .mdi {
  316. font-size: 42px;
  317. color: #fff;
  318. }
  319. div {
  320. position: absolute;
  321. top: 50%;
  322. left: 50%;
  323. transform: translate(-50%, -50%);
  324. border-radius: 50%;
  325. }
  326. }
  327. </style>