studentInfoForm.vue 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  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 { getUserAvatar } from '@/utils/avatar'
  30. import { uploadFile } from '@/api/common'
  31. import Snackbar from '@/plugins/snackbar'
  32. import { useI18n } from '@/hooks/web/useI18n'; const { t } = useI18n()
  33. import { isValidIdCard18 } from '@/utils/validate'
  34. import { getSchoolList, getDepartmentListBySchoolId, getMajorList } from '@/api/recruit/personal/student'
  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. // 专业名称下拉列表
  50. let majorName = null
  51. const getMajorData = async (name) => {
  52. const item = items.value.options.find(e => e.key === 'majorId')
  53. if (!item) return
  54. if (item.items?.length && (majorName === name)) return // 防抖
  55. majorName = name
  56. if (name) {
  57. const data = await getMajorList({ name })
  58. item.items = data
  59. }
  60. }
  61. const items = ref({
  62. options: [
  63. {
  64. slotName: 'avatar',
  65. key: 'avatar',
  66. value: '',
  67. flexStyle: 'align-center mb-3'
  68. },
  69. {
  70. type: 'text',
  71. key: 'name',
  72. value: '',
  73. default: null,
  74. label: '姓名 *',
  75. outlined: true,
  76. rules: [
  77. value => {
  78. if (value) return true
  79. return '请输入您的中文名'
  80. },
  81. value => {
  82. var regex = /^[\u4e00-\u9fa5]+$/
  83. if (regex.test(value)) return true
  84. return '请输入正确的中文名'
  85. }
  86. ]
  87. },
  88. {
  89. type: 'autocomplete',
  90. key: 'sex',
  91. value: '1', // '1' ? '男' : '女'
  92. default: '1',
  93. label: '性别 *',
  94. outlined: true,
  95. dictTypeName: 'menduner_sex',
  96. rules: [v => !!v || '请选择性别'],
  97. items: [],
  98. change: val => male.value = val
  99. },
  100. {
  101. type: 'phoneNumber',
  102. key: 'phone',
  103. value: '',
  104. clearable: true,
  105. label: '联系手机号 *',
  106. rules: [v => !!v || '请填写联系手机号']
  107. },
  108. {
  109. type: 'datePicker',
  110. mode: 'date',
  111. labelWidth: 80,
  112. key: 'birthday',
  113. value: new Date(1990, 0, 1).getTime(), // 月份是从 0 开始的
  114. label: '出生日期 *',
  115. disabledFutureDates: true,
  116. format: 'YYYY/MM/DD',
  117. flexStyle: 'mb-7',
  118. outlined: true,
  119. rules: [v => !!v || '请选择出生日期']
  120. },
  121. {
  122. type: 'autocomplete',
  123. key: 'schoolId',
  124. value: null,
  125. default: null,
  126. label: '就读学校 *',
  127. outlined: true,
  128. itemText: 'name',
  129. itemValue: 'schoolId',
  130. rules: [v => !!v || '请选择就读学校'],
  131. items: [],
  132. change: e => getDepartmentList(e, 'schoolDeptId', 0),
  133. },
  134. {
  135. type: 'autocomplete',
  136. key: 'schoolDeptId',
  137. value: null,
  138. default: null,
  139. label: '所在院系 *',
  140. outlined: true,
  141. itemText: 'name',
  142. itemValue: 'id',
  143. rules: [v => !!v || '请选择所在院系'],
  144. change: e => getDepartmentList(e, 'schoolClassId', 2),
  145. items: []
  146. },
  147. {
  148. type: 'combobox',
  149. key: 'schoolClassId',
  150. value: null,
  151. label: '所在班级 *',
  152. outlined: true,
  153. clearable: true,
  154. canBeInputted: true,
  155. itemTextName: 'schoolClassName',
  156. itemText: 'name',
  157. itemValue: 'id',
  158. rules: [v => !!v || '请选择所在班级'],
  159. items: []
  160. },
  161. {
  162. type: 'autocomplete',
  163. key: 'majorId',
  164. value: null,
  165. label: '所学专业 *',
  166. outlined: true,
  167. itemText: 'nameCn',
  168. itemValue: 'id',
  169. rules: [v => !!v || '请选择所学专业'],
  170. search: e => getMajorData(e),
  171. items: []
  172. },
  173. {
  174. type: 'text',
  175. key: 'studentNo',
  176. value: '',
  177. default: null,
  178. label: '学号 *',
  179. outlined: true,
  180. rules: [v => !!v || '请填写学号']
  181. },
  182. {
  183. type: 'text',
  184. key: 'emergencyContactName',
  185. value: '',
  186. default: null,
  187. label: '紧急联系人姓名',
  188. outlined: true
  189. },
  190. {
  191. type: 'phoneNumber',
  192. key: 'emergencyContactPhone',
  193. value: '',
  194. clearable: true,
  195. label: '紧急联系人手机号'
  196. },
  197. ]
  198. })
  199. // 学校下拉列表
  200. const getSchoolListData = async () => {
  201. const item = items.value.options.find(e => e.key === 'schoolId')
  202. if (!item) return
  203. const data = await getSchoolList()
  204. item.items = data || []
  205. }
  206. getSchoolListData()
  207. // 根据学校id获取院系、班级列表
  208. const getDepartmentList = async (id, key, type) => {
  209. const item = items.value.options.find(e => e.key === key)
  210. if (!item) return
  211. let params = { type } // type: 0院系|1专业|2班级
  212. // 查院系用schoolId,查班级用parentId
  213. if (key === 'schoolDeptId') params.schoolId = id
  214. else params.parentId = id
  215. const data = await getDepartmentListBySchoolId(params)
  216. item.items = data || []
  217. }
  218. // 选择文件
  219. const fileInput = ref()
  220. const clicked = ref(false)
  221. const openFileInput = () => {
  222. if (clicked.value) return
  223. clicked.value = true
  224. fileInput.value.click()
  225. clicked.value = false
  226. }
  227. // 上传头像
  228. const accept = ['jpg', 'png', 'jpeg']
  229. const handleUploadFile = async (e) => {
  230. const file = e.target.files[0]
  231. if (!file) return
  232. const arr = file.name.split('.')
  233. const fileType = arr?.length ? arr[arr.length-1] : ''
  234. if (!accept.includes(fileType)) return Snackbar.warning('请上传图片格式文件')
  235. const size = file.size
  236. if (size / (1024*1024) > 20) {
  237. Snackbar.warning(t('common.fileSizeExceed'))
  238. return
  239. }
  240. const reader = new FileReader()
  241. reader.readAsDataURL(file)
  242. reader.onload = () => {
  243. selectPic.value = String(reader.result)
  244. isShowCopper.value = true
  245. }
  246. }
  247. const handleHideCopper = (data) => {
  248. isShowCopper.value = false
  249. if (data) {
  250. const { file } = data
  251. if (!file) return
  252. const formData = new FormData()
  253. formData.append('file', file)
  254. formData.append('path', 'img')
  255. uploadFile(formData).then(async ({ data }) => {
  256. if (!data) return
  257. items.value.options.find(e => e.key === 'avatar').value = data
  258. })
  259. }
  260. }
  261. // 获取字典内容
  262. const getDictData = async (dictTypeName, key) => {
  263. const item = items.value.options.find(e => e.key === key)
  264. if (item) {
  265. const apiType = dictTypeName === 'positionSecondData' ? 'positionSecondData' : 'dict'
  266. const { data } = await getDict(dictTypeName, apiType === 'dict' ? null : {}, apiType)
  267. item.items = data
  268. }
  269. }
  270. const userInfo = ref(localStorage.getItem('userInfo') ? JSON.parse(localStorage.getItem('userInfo')) : {})
  271. const baseInfo = ref(localStorage.getItem('baseInfo') ? JSON.parse(localStorage.getItem('baseInfo')) : {})
  272. items.value.options.forEach((e) => {
  273. if (e.dictTypeName) getDictData(e.dictTypeName, e.key) // 查字典set options
  274. if (userInfo.value && userInfo.value[e.key]) e.value = userInfo.value[e.key] // 人才信息回显
  275. if (baseInfo.value && baseInfo.value[e.key]) e.value = baseInfo.value[e.key] // 人才信息回显
  276. if (e.key === 'sex' && e.value === '0') e.value = e.default
  277. if (setInfo.value[e.key]) e.value = setInfo.value[e.key]
  278. })
  279. const getQuery = async () => {
  280. const { valid } = await formPageRef.value.formRef.validate()
  281. if (!valid) {
  282. Snackbar.warning('请填写完整个人信息!')
  283. return false
  284. }
  285. const obj = {}
  286. items.value.options.forEach(e => {
  287. if (Object.prototype.hasOwnProperty.call(e, 'data')) return obj[e.key] = e.data
  288. if (e.key === 'schoolClassId') {
  289. const classObj = e.items.find(k => k[e.itemValue] === e.value)
  290. if (!classObj) {
  291. obj[e.key] = null
  292. obj[e.itemTextName] = e.value
  293. } else obj[e.key] = e.value
  294. } else obj[e.key] = e.value === '' ? null : e.value
  295. })
  296. if (!obj.avatar) obj.avatar = getUserAvatar(null, obj.sex)
  297. query = Object.assign(query, obj)
  298. return query
  299. }
  300. defineExpose({
  301. getQuery
  302. })
  303. </script>
  304. <style scoped lang="scss">
  305. .avatarsBox {
  306. height: 80px;
  307. width: 80px;
  308. position: relative;
  309. cursor: pointer;
  310. // margin: 32px;
  311. // margin-right: 40px;
  312. margin: 0 40px 0 32px;
  313. .img {
  314. width: 100%;
  315. height: 100%;
  316. }
  317. .mdi {
  318. font-size: 42px;
  319. color: #fff;
  320. }
  321. div {
  322. position: absolute;
  323. top: 50%;
  324. left: 50%;
  325. transform: translate(-50%, -50%);
  326. border-radius: 50%;
  327. }
  328. }
  329. </style>