studentInfoForm.vue 10 KB

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