|
@@ -0,0 +1,375 @@
|
|
|
+<template>
|
|
|
+ <div style="width: 100%;">
|
|
|
+ <CtForm ref="formPageRef" :items="items">
|
|
|
+ <!-- 头像 -->
|
|
|
+ <template #avatar="{ item }">
|
|
|
+ <div style="color: #7a7a7a; min-width: 52px;">头像:</div>
|
|
|
+ <div class="avatarsBox" @mouseover="showIcon = true" @mouseleave="showIcon = false">
|
|
|
+ <v-avatar class="elevation-5" size=80 :image="getUserAvatar(item.value, male)"></v-avatar>
|
|
|
+ <div v-show="showIcon" @click="openFileInput" v-bind="$attrs" class="mdi mdi-camera-outline">
|
|
|
+ <input
|
|
|
+ type="file"
|
|
|
+ ref="fileInput"
|
|
|
+ accept="image/png, image/jpg, image/jpeg"
|
|
|
+ style="display: none;"
|
|
|
+ @change="handleUploadFile"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div style="font-size: 14px; color: var(--color-999);">只支持JPG、JPEG、PNG类型的图片,大小不超过20M</div>
|
|
|
+ </template>
|
|
|
+ </CtForm>
|
|
|
+ </div>
|
|
|
+ <ImgCropper :visible="isShowCopper" :image="selectPic" :cropBoxResizable="true" @submit="handleHideCopper" :aspectRatio="1 / 1" @close="isShowCopper = false"></ImgCropper>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { getDict } from '@/hooks/web/useDictionaries'
|
|
|
+defineOptions({name: 'dialogExtend-InfoForm'})
|
|
|
+import { reactive, ref } from 'vue'
|
|
|
+import { schoolMajorByName, schoolSearchByName } from '@/api/recruit/personal/resume'
|
|
|
+import { getUserAvatar } from '@/utils/avatar'
|
|
|
+import { uploadFile } from '@/api/common'
|
|
|
+import Snackbar from '@/plugins/snackbar'
|
|
|
+import { useI18n } from '@/hooks/web/useI18n'; const { t } = useI18n()
|
|
|
+import { debounce } from 'lodash'
|
|
|
+import { dealCanBeInputtedSave } from '@/utils/getText'
|
|
|
+import { isValidIdCard18 } from '@/utils/validate'
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ option: {
|
|
|
+ type: Object,
|
|
|
+ default: () => {}
|
|
|
+ }
|
|
|
+})
|
|
|
+const setInfo = ref(props.option?.setInfo ? props.option.setInfo : {})
|
|
|
+const formPageRef = ref()
|
|
|
+let query = reactive({})
|
|
|
+
|
|
|
+// 学校下拉列表
|
|
|
+const schoolNameInput = ref('')
|
|
|
+const getSchoolListData = async (name) => {
|
|
|
+ const item = items.value.options.find(e => e.key === 'schoolId')
|
|
|
+ if (!item) return
|
|
|
+ if (item.items?.length && (schoolNameInput.value === name)) return // 防抖
|
|
|
+ item[item.itemTextName] = schoolNameInput.value = name
|
|
|
+
|
|
|
+ if (name === null || name === '') { item.items = [] }
|
|
|
+ else {
|
|
|
+ const data = await schoolSearchByName({ name })
|
|
|
+ item.items = data
|
|
|
+ }
|
|
|
+}
|
|
|
+const debouncedCallbackSchool = debounce(newValue => {
|
|
|
+ if (!newValue) return
|
|
|
+ getSchoolListData(newValue)
|
|
|
+}, 500)
|
|
|
+
|
|
|
+// 专业下拉列表
|
|
|
+const majorNameInput = ref('')
|
|
|
+const getMajorListData = async (name) => {
|
|
|
+ const item = items.value.options.find(e => e.key === 'majorId')
|
|
|
+ if (name === '') { // 此接口不支持传空值
|
|
|
+ item.items = []
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (item.items?.length && (majorNameInput.value === name)) return // 防抖
|
|
|
+ item[item.itemTextName] = majorNameInput.value = name
|
|
|
+
|
|
|
+ if (name === null || name === '') { item.items = [] }
|
|
|
+ else {
|
|
|
+ const data = await schoolMajorByName({ name })
|
|
|
+ item.items = data
|
|
|
+ }
|
|
|
+}
|
|
|
+const debouncedCallbackMajor = debounce(newValue => {
|
|
|
+ getMajorListData(newValue)
|
|
|
+}, 500)
|
|
|
+
|
|
|
+// 图片裁剪
|
|
|
+const selectPic = ref('')
|
|
|
+const isShowCopper = ref(false)
|
|
|
+
|
|
|
+const male = ref('1')
|
|
|
+const showIcon = ref(false)
|
|
|
+const items = ref({
|
|
|
+ options: [
|
|
|
+ {
|
|
|
+ slotName: 'avatar',
|
|
|
+ key: 'avatar',
|
|
|
+ value: '',
|
|
|
+ flexStyle: 'align-center mb-3'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'text',
|
|
|
+ key: 'name',
|
|
|
+ value: '',
|
|
|
+ default: null,
|
|
|
+ label: '姓名 *',
|
|
|
+ outlined: true,
|
|
|
+ rules: [
|
|
|
+ value => {
|
|
|
+ if (value) return true
|
|
|
+ return '请输入您的中文名'
|
|
|
+ },
|
|
|
+ value => {
|
|
|
+ var regex = /^[\u4e00-\u9fa5]+$/
|
|
|
+ if (regex.test(value)) return true
|
|
|
+ return '请输入正确的中文名'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'autocomplete',
|
|
|
+ key: 'sex',
|
|
|
+ value: '1', // '1' ? '男' : '女'
|
|
|
+ default: '1',
|
|
|
+ label: '性别 *',
|
|
|
+ outlined: true,
|
|
|
+ dictTypeName: 'menduner_sex',
|
|
|
+ rules: [v => !!v || '请选择性别'],
|
|
|
+ items: [],
|
|
|
+ change: val => male.value = val
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'text',
|
|
|
+ key: 'idCardNo',
|
|
|
+ value: '',
|
|
|
+ label: '身份证号码 *',
|
|
|
+ rules: [
|
|
|
+ value => {
|
|
|
+ if (!value) {
|
|
|
+ return '请输入您的身份证号码'
|
|
|
+ }
|
|
|
+ return true
|
|
|
+ },
|
|
|
+ value => {
|
|
|
+ if (!isValidIdCard18(value)) {
|
|
|
+ return '请输入正确的身份证号码'
|
|
|
+ }
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'phoneNumber',
|
|
|
+ key: 'phone',
|
|
|
+ value: '',
|
|
|
+ clearable: true,
|
|
|
+ label: '联系手机号 *',
|
|
|
+ rules: [v => !!v || '请填写联系手机号']
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'datePicker',
|
|
|
+ mode: 'date',
|
|
|
+ labelWidth: 80,
|
|
|
+ key: 'birthday',
|
|
|
+ value: '1990-01-01',
|
|
|
+ defaultValue: new Date(1990, 1, 1),
|
|
|
+ label: '出生日期 *',
|
|
|
+ disabledFutureDates: true,
|
|
|
+ format: 'YYYY/MM/DD',
|
|
|
+ flexStyle: 'mb-7',
|
|
|
+ outlined: true,
|
|
|
+ rules: [v => !!v || '请选择出生日期']
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'combobox',
|
|
|
+ key: 'schoolId',
|
|
|
+ value: null,
|
|
|
+ default: null,
|
|
|
+ label: '学校名称 *',
|
|
|
+ outlined: true,
|
|
|
+ clearable: true,
|
|
|
+ canBeInputted: true, //
|
|
|
+ itemTextName: 'schoolName',
|
|
|
+ itemText: 'value',
|
|
|
+ itemValue: 'key',
|
|
|
+ rules: [v => !!v || '请选择学校名称'],
|
|
|
+ search: debouncedCallbackSchool,
|
|
|
+ items: [],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'text',
|
|
|
+ key: 'schoolDepartmentName',
|
|
|
+ value: '',
|
|
|
+ default: null,
|
|
|
+ label: '所在院系 *',
|
|
|
+ outlined: true,
|
|
|
+ rules: [v => !!v || '请填写所在院系']
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'combobox',
|
|
|
+ key: 'majorId',
|
|
|
+ value: null,
|
|
|
+ default: null,
|
|
|
+ label: '所学专业 *',
|
|
|
+ outlined: true,
|
|
|
+ clearable: true,
|
|
|
+ canBeInputted: true, //
|
|
|
+ itemTextName: 'major',
|
|
|
+ itemText: 'nameCn',
|
|
|
+ itemValue: 'id',
|
|
|
+ rules: [v => !!v || '请选择所学专业'],
|
|
|
+ search: debouncedCallbackMajor,
|
|
|
+ items: []
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'text',
|
|
|
+ key: 'schoolClassName',
|
|
|
+ value: '',
|
|
|
+ default: null,
|
|
|
+ label: '所在班级 *',
|
|
|
+ outlined: true,
|
|
|
+ rules: [v => !!v || '请填写所在班级']
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'text',
|
|
|
+ key: 'studentNo',
|
|
|
+ value: '',
|
|
|
+ default: null,
|
|
|
+ label: '学号 *',
|
|
|
+ outlined: true,
|
|
|
+ rules: [v => !!v || '请填写学号']
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'text',
|
|
|
+ key: 'emergencyContactName',
|
|
|
+ value: '',
|
|
|
+ default: null,
|
|
|
+ label: '紧急联系人姓名 *',
|
|
|
+ outlined: true,
|
|
|
+ rules: [v => !!v || '请填写紧急联系人姓名']
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'phoneNumber',
|
|
|
+ key: 'emergencyContactPhone',
|
|
|
+ value: '',
|
|
|
+ clearable: true,
|
|
|
+ label: '紧急联系人手机号 *',
|
|
|
+ rules: [v => !!v || '请填写紧急联系人手机号']
|
|
|
+ },
|
|
|
+ ]
|
|
|
+})
|
|
|
+
|
|
|
+if (import.meta.env.VITE_NODE_ENV === 'production') {
|
|
|
+ items.value.options = items.value.options.filter(e => e.slotName !== 'analysis')
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// 选择文件
|
|
|
+const fileInput = ref()
|
|
|
+const clicked = ref(false)
|
|
|
+const openFileInput = () => {
|
|
|
+ if (clicked.value) return
|
|
|
+ clicked.value = true
|
|
|
+ fileInput.value.click()
|
|
|
+ clicked.value = false
|
|
|
+}
|
|
|
+
|
|
|
+// 上传头像
|
|
|
+const accept = ['jpg', 'png', 'jpeg']
|
|
|
+const handleUploadFile = async (e) => {
|
|
|
+ const file = e.target.files[0]
|
|
|
+ if (!file) return
|
|
|
+
|
|
|
+ const arr = file.name.split('.')
|
|
|
+ const fileType = arr?.length ? arr[arr.length-1] : ''
|
|
|
+ if (!accept.includes(fileType)) return Snackbar.warning('请上传图片格式文件')
|
|
|
+
|
|
|
+ const size = file.size
|
|
|
+ if (size / (1024*1024) > 20) {
|
|
|
+ Snackbar.warning(t('common.fileSizeExceed'))
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const reader = new FileReader()
|
|
|
+ reader.readAsDataURL(file)
|
|
|
+ reader.onload = () => {
|
|
|
+ selectPic.value = String(reader.result)
|
|
|
+ isShowCopper.value = true
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleHideCopper = (data) => {
|
|
|
+ isShowCopper.value = false
|
|
|
+ if (data) {
|
|
|
+ const { file } = data
|
|
|
+ if (!file) return
|
|
|
+
|
|
|
+ const formData = new FormData()
|
|
|
+ formData.append('file', file)
|
|
|
+ formData.append('path', 'img')
|
|
|
+ uploadFile(formData).then(async ({ data }) => {
|
|
|
+ if (!data) return
|
|
|
+ items.value.options.find(e => e.key === 'avatar').value = data
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 获取字典内容
|
|
|
+const getDictData = async (dictTypeName, key) => {
|
|
|
+ const item = items.value.options.find(e => e.key === key)
|
|
|
+ if (item) {
|
|
|
+ const apiType = dictTypeName === 'positionSecondData' ? 'positionSecondData' : 'dict'
|
|
|
+ const { data } = await getDict(dictTypeName, apiType === 'dict' ? null : {}, apiType)
|
|
|
+ item.items = data
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const userInfo = ref(localStorage.getItem('userInfo') ? JSON.parse(localStorage.getItem('userInfo')) : {})
|
|
|
+const baseInfo = ref(localStorage.getItem('baseInfo') ? JSON.parse(localStorage.getItem('baseInfo')) : {})
|
|
|
+items.value.options.forEach((e) => {
|
|
|
+ if (e.dictTypeName) getDictData(e.dictTypeName, e.key) // 查字典set options
|
|
|
+ if (userInfo.value && userInfo.value[e.key]) e.value = userInfo.value[e.key] // 人才信息回显
|
|
|
+ if (baseInfo.value && baseInfo.value[e.key]) e.value = baseInfo.value[e.key] // 人才信息回显
|
|
|
+ if (e.key === 'sex' && e.value === '0') e.value = e.default
|
|
|
+ if (setInfo.value[e.key]) e.value = setInfo.value[e.key]
|
|
|
+})
|
|
|
+
|
|
|
+const getQuery = async () => {
|
|
|
+ const { valid } = await formPageRef.value.formRef.validate()
|
|
|
+ if (!valid) return false
|
|
|
+ const obj = {}
|
|
|
+ items.value.options.forEach(e => {
|
|
|
+ if (Object.prototype.hasOwnProperty.call(e, 'data')) return obj[e.key] = e.data
|
|
|
+ obj[e.key] = e.value === '' ? null : e.value
|
|
|
+ if (e.canBeInputted) { // 特殊处理可输入下拉框
|
|
|
+ dealCanBeInputtedSave(e, obj)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ if (!obj.avatar) obj.avatar = getUserAvatar(null, obj.sex)
|
|
|
+ query = Object.assign(query, obj)
|
|
|
+ return query
|
|
|
+}
|
|
|
+
|
|
|
+defineExpose({
|
|
|
+ getQuery
|
|
|
+})
|
|
|
+</script>
|
|
|
+<style scoped lang="scss">
|
|
|
+.avatarsBox {
|
|
|
+ height: 80px;
|
|
|
+ width: 80px;
|
|
|
+ position: relative;
|
|
|
+ cursor: pointer;
|
|
|
+ // margin: 32px;
|
|
|
+ // margin-right: 40px;
|
|
|
+ margin: 0 40px 0 32px;
|
|
|
+ .img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ }
|
|
|
+ .mdi {
|
|
|
+ font-size: 42px;
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+ div {
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ border-radius: 50%;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|