Browse Source

特殊处理可输入下拉框

lifanagju_citu 11 months ago
parent
commit
026cdd8282

+ 1 - 0
components.d.ts

@@ -10,6 +10,7 @@ declare module 'vue' {
     AreaSelect: typeof import('./src/components/AreaSelect/index.vue')['default']
     Autocomplete: typeof import('./src/components/FormUI/autocomplete/index.vue')['default']
     Combobox: typeof import('./src/components/FormUI/combobox/index.vue')['default']
+    ComboboxZhAndEn: typeof import('./src/components/FormUI/comboboxZhAndEn/index.vue')['default']
     copy: typeof import('./src/components/CtForm/index copy.vue')['default']
     CtBtn: typeof import('./src/components/CtVuetify/CtBtn/index.vue')['default']
     CtDialog: typeof import('./src/components/CtDialog/index.vue')['default']

+ 31 - 1
src/api/resume.js

@@ -60,6 +60,28 @@ export const saveResumeEduExp = async (data) => {
   })
 }
 
+// 获取-工作经历
+export const getResumeWorkExp = async () => {
+  return await request.get({
+    url: '/app-api/menduner/system/person/resume/get/work/exp'
+  })
+}
+
+// 删除-工作经历
+export const deleteResumeWorkExp = async (id) => {
+  return await request.delete({
+    url: '/app-api/menduner/system/person/resume/remove/work/exp?id=' + id
+  })
+}
+
+// 保存-工作经历
+export const saveResumeWorkExp = async (data) => {
+  return await request.post({
+    url: '/app-api/menduner/system/person/resume/save/work/exp',
+    data
+  })
+}
+
 // 保存项目经历
 export const saveResumeProjectExp = async (data) => {
   return await request.post({
@@ -140,4 +162,12 @@ export const schoolSearchByName = async (params) => {
     url: '/app-api/menduner/system/school/search/by/name',
     params
   })
-}
+}
+
+// 根据企业名称模糊搜索
+export const enterpriseSearchByName = async (params) => {
+  return await request.get({
+    url: '/app-api/menduner/system/enterprise/search/by/name',
+    params
+  })
+}

+ 9 - 0
src/components/CtForm/index.vue

@@ -29,6 +29,14 @@
                 @search="val => item.search ? item.search(val) : null"
                 @change="handleChange(item)"
               ></comboboxUI>
+              <!-- comboboxZhAndEnUI暂不可用 -->
+              <comboboxZhAndEnUI 
+                v-if="item.type === 'comboboxZhAndEn'"
+                v-model="item.value"
+                :item="item"
+                @search="val => item.search ? item.search(val) : null"
+                @change="handleChange(item)"
+              ></comboboxZhAndEnUI>
               <radioGroupUI
                 v-if="item.type === 'ifRadio'"
                 v-model="item.value"
@@ -63,6 +71,7 @@ defineOptions({ name:'components-ct-form'})
 import textUI from './../FormUI/TextInput'
 import autocompleteUI from './../FormUI/autocomplete'
 import comboboxUI from './../FormUI/combobox'
+import comboboxZhAndEnUI from './../FormUI/comboboxZhAndEn'
 import radioGroupUI from './../FormUI/radioGroup'
 import textareaUI from './../FormUI/textArea'
 import datePicker from '../FormUI/datePicker'

+ 41 - 0
src/components/FormUI/comboboxZhAndEn/index.vue

@@ -0,0 +1,41 @@
+<template>
+  <div :style="{ width: item.width ? item.width + 'px' : '100%' }">
+    <v-combobox
+      v-model="value"
+      :rules="item.rules"
+      :attach="!item.noAttach"
+      :label="item.label"
+      :placeholder="item.placeholder || item.label"
+      :item-title="item.itemText || 'label'"
+      :item-value="item.itemValue || 'value'"
+      :items="item.items"
+      variant="outlined"
+      :density="item.dense || 'compact'"
+      :clearable="item.clearable"
+      :disabled="item.disabled"
+      :return-object="item.returnObject || false"
+      @update:search="search"
+      @update:modelValue="modelValueUpDate"
+    ></v-combobox>
+  </div>
+</template>
+<script setup>
+import { ref, defineEmits } from 'vue';
+defineOptions({ name:'FormUI-v-combobox'})
+
+const props = defineProps({item: Object, modelValue: [String, Number]})
+const emit = defineEmits(['update:modelValue', 'change', 'search'])
+const item = props.item
+const value = ref(props.modelValue)
+const modelValueUpDate = (val) => {
+  value.value = val
+  emit('update:modelValue', value.value)
+  // let obj = null // if (item.getObject) obj = item.items.find(e => e[item.itemValue] === value.value) || null
+  emit('change', value.value)
+}
+const search = (val) => {
+  emit('search', val)
+}
+</script>
+<style lang="scss" scoped>
+</style>

+ 11 - 2
src/utils/getText.js

@@ -1,7 +1,16 @@
 export const getText = (value, arr, itemText = 'label', itemValue = 'value') => { // 一维数组
   // console.log('getText', value, arr)
   if (!arr?.length || !(value && value !==0)) return
-  const item = arr.find(e => e[itemValue] === value)
+  const item = arr.find(formItem => formItem[itemValue] === value)
   if (!item) return
   return item[itemText]
-}
+}
+
+export const dealCanBeInputtedValueAndLabel = (formItem, item) => {
+  if (item[formItem.key] && item[formItem.itemTextName]) { formItem.search(item[formItem.itemTextName], '触发下拉框内容'); formItem.value = item[formItem.key] }
+  else { formItem.value = item[formItem.itemTextName]; formItem[formItem.itemTextName] = item[formItem.itemTextName] }
+}
+export const dealCanBeInputtedSave = (formItem, params) => {
+  if (formItem.value === formItem[formItem.itemTextName]) { params[formItem.key] = ''; params[formItem.itemTextName] = formItem[formItem.itemTextName] }
+  else { params[formItem.key] = formItem.value; params[formItem.itemTextName] = formItem[formItem.itemTextName] }
+}

+ 31 - 17
src/views/resume/components/educationExp.vue

@@ -37,7 +37,6 @@
         <div class="level2 my-2">
           <span class="color6 font15">{{ item.major }}</span>
           <span class="vline"  v-if="item.educationSystemType"></span>
-          <!-- <span class="color6 font15">{{ getText(item.educationSystemType, dictItemsObj.educationType) }}</span> -->
           <span class="color6 font15">{{ getText(item.educationSystemType, dictItemsObj.educationSystemType) }}</span>
         </div>
         <div class="level3">
@@ -55,7 +54,7 @@ import { getDict } from '@/hooks/web/useDictionaries'
 import { getTimeStamp, timesTampChange } from '@/utils/date'
 import { saveResumeEduExp, getResumeEduExp, deleteResumeEduExp, schoolSearchByName, schoolMajorByName } from '@/api/resume'
 import Confirm from '@/plugins/confirm'
-import { getText } from '@/utils/getText'
+import { getText, dealCanBeInputtedSave, dealCanBeInputtedValueAndLabel } from '@/utils/getText'
 import { debounce } from 'lodash'
 import { nextTick, shallowRef, reactive, ref } from 'vue'
 const editId = ref(null)
@@ -64,11 +63,13 @@ const dictItemsObj = reactive({})
 dictItemsObj.educationSystemType = [{ label: '全日制', value: '0' }, { label: '非全日制', value: '1' }]
 
 // 学校下拉列表
-const schoolName = ref('')
-const getSchoolListData = async (name) => {
-  schoolName.value = name
+// const schoolName = ref('')
+const getSchoolListData = async (name, init = '') => {
+  // schoolName.value = name
   const item = formItems.value.options.find(e => e.key === 'schoolId')
   if (!item) return
+  item[item.itemTextName] = name
+  if (!init) item.value = null
   const data = await schoolSearchByName({ name })
   item.items = data
 }
@@ -77,11 +78,11 @@ const debouncedCallbackSchool = debounce(newValue => {
 }, 500)
 
 // 专业下拉列表
-const major = ref('')
-const getMajorListData = async (name) => {
-  major.value = name
+const getMajorListData = async (name, init = '') => {
+  if (name === '') return // 此接口不支持传空值
   const item = formItems.value.options.find(e => e.key === 'majorId')
-  if (!item) return
+  item[item.itemTextName] = name
+  if (!init) item.value = null
   const data = await schoolMajorByName({ name })
   item.items = data
 }
@@ -100,6 +101,8 @@ const formItems = ref({
       col: 6,
       outlined: true,
       clearable: true,
+      canBeInputted: true, //
+      // itemValueName: 'schoolId',
       itemTextName: 'schoolName',
       itemText: 'value',
       itemValue: 'key',
@@ -116,6 +119,8 @@ const formItems = ref({
       col: 6,
       outlined: true,
       clearable: true,
+      canBeInputted: true, //
+      // itemValueName: 'majorId',
       itemTextName: 'major',
       itemText: 'nameCn',
       itemValue: 'id',
@@ -199,9 +204,13 @@ const handle = (item) => {
   if (item) { // 编辑
     editId.value = item.id
     formItems.value.options.forEach(e => { // 回显
-      if (item[e.key]) e.value = item[e.key]
-      if (e.type === 'datepicker') e.value = timesTampChange(item[e.key]).slice(0, 10)
-      if (e.type === 'combobox') e.value = item[e.itemTextName] // 回显中文,因为下拉框无内容,显示id无用
+      if (e.canBeInputted) { // 特殊处理可输入下拉框
+        dealCanBeInputtedValueAndLabel(e, item)
+        // if (item[e.key] && item[e.itemTextName]) { e.search(item[e.itemTextName], '触发下拉框内容'); e.value = item[e.key] }
+        // else { e.value = item[e.itemTextName]; e[e.itemTextName] = item[e.itemTextName] }
+      }
+      else if (e.type === 'datepicker') e.value = timesTampChange(item[e.key]).slice(0, 10)
+      else if (item[e.key]) e.value = item[e.key]
     })
   } else { // 新增
     editId.value = null
@@ -218,11 +227,14 @@ const handleSave = async () => {
   if (!valid) return
   const obj = {}
   formItems.value.options.forEach(e => {
-    if (e.type === 'datepicker') obj[e.key] = getTimeStamp(e.value)
+    if (e.canBeInputted) { // 特殊处理可输入下拉框
+      dealCanBeInputtedSave(e, obj)
+      // if (e.value === e[e.itemTextName]) { obj[e.key] = ''; obj[e.itemTextName] = e[e.itemTextName] }
+      // else { obj[e.key] = e.value; obj[e.itemTextName] = e[e.itemTextName] }
+    }
+    else if (e.type === 'datepicker') obj[e.key] = getTimeStamp(e.value)
     else obj[e.key] = e.value
   })
-  obj.schoolName = schoolName.value
-  obj.major = major.value
   if (editId.value) obj.id = editId.value
   await saveResumeEduExp(obj)
   Snackbar.success('保存成功!')
@@ -234,8 +246,10 @@ const handleSave = async () => {
 const handleDelete = ({ id }) => {
   Confirm('系统提示', '是否确认删除此教育经历?').then(async () => {
     await deleteResumeEduExp(id)
-    getData(id)
-    Snackbar.success('删除成功!')
+    nextTick(() => {
+      getData(id)
+      Snackbar.success('删除成功!')
+    })
   })
 }
 

+ 252 - 0
src/views/resume/components/workExperience.vue

@@ -0,0 +1,252 @@
+<template>
+  <div class="resume-box">
+    <div class="resume-header mb-3">
+      <div class="resume-title">{{ $t('resume.workExperience') }}</div>
+      <v-btn  variant="text" color="primary" prepend-icon="mdi-plus-box" @click="handle(0)">{{ $t('common.add') }}</v-btn>
+    </div>
+    <!-- 编辑-表单 -->
+    <div v-if="isEdit" class="educExpItem-edit">
+      <h4 class="color6 my-3 mx-2"> {{ titleStatus ? $t('common.edit') : $t('common.add') }}{{ $t('resume.workExperience') }}</h4>
+      <CtForm ref="CtFormRef" :items="formItems" style="width: 100%;"></CtForm>
+      <div class="text-end mt-3">
+        <v-btn class="half-button mr-3" variant="tonal" @click="isEdit = false">{{ $t('common.cancel') }}</v-btn>
+        <v-btn color="primary" class="half-button" @click="handleSave">{{ $t('common.save') }}</v-btn>
+      </div>
+    </div>
+    <!-- 展示 -->
+    <div v-else-if="!dataList?.length" class="resumeNoDataText">{{ $t('resume.dataDefaultPrompt') }}{{ $t('resume.workExperience') }}...</div>
+    <div
+      v-else
+      v-for="(item, index) in dataList" :key="'workExperience' + index"
+      :class="[' mx-n2', {'mt-5': index }]"
+    >
+      <div class="educExpItem" @mouseenter="item.active = true" @mouseleave="item.active = false">
+        <div class="level1 d-flex align-center justify-space-between" style="height: 40px;">
+          <div>
+            <span style="font-size: 18px; font-weight: bold;">{{ item.enterpriseName }}</span>
+            <span class="color6 font15 ml-5">
+              <span>{{ timesTampChange(item.startTime).slice(0, 10) }}</span>
+              <span class="mx-1">至</span>
+              <span>{{ timesTampChange(item.endTime).slice(0, 10) }}</span>
+            </span>
+          </div>
+          <div v-if="item.active">
+            <v-btn  variant="text" color="primary" prepend-icon="mdi-square-edit-outline" @click="handle(item)">{{ $t('common.edit') }}</v-btn>
+            <v-btn  variant="text" color="primary" prepend-icon="mdi-trash-can-outline" @click="handleDelete(item)">{{ $t('common.delete') }}</v-btn>
+          </div>
+        </div>
+        <div class="level2 my-2">
+          <span class="color6 font15">{{ item.positionName }}</span>
+        </div>
+        <div class="level3">
+          <span class="color6 font15">工作内容:{{ item.content }}</span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup name="workExperience">
+import CtForm from '@/components/CtForm'
+import Snackbar from '@/plugins/snackbar'
+import { getDict } from '@/hooks/web/useDictionaries'
+import { getTimeStamp, timesTampChange } from '@/utils/date'
+import { saveResumeWorkExp, getResumeWorkExp, deleteResumeWorkExp, enterpriseSearchByName } from '@/api/resume'
+import Confirm from '@/plugins/confirm'
+import { dealCanBeInputtedSave, dealCanBeInputtedValueAndLabel } from '@/utils/getText'
+import { debounce } from 'lodash'
+import { nextTick, shallowRef, reactive, ref } from 'vue'
+const editId = ref(null)
+const CtFormRef = ref()
+const dictItemsObj = reactive({})
+dictItemsObj.educationSystemType = [{ label: '全日制', value: '0' }, { label: '非全日制', value: '1' }]
+
+// 企业名称下拉列表
+const getSchoolListData = async (name, init = '') => {
+  const item = formItems.value.options.find(e => e.key === 'enterpriseId')
+  if (!item) return
+  if (!init) item.value = null
+  item[item.itemTextName] = name
+  const data = await enterpriseSearchByName({ name })
+  item.items = data
+}
+const debouncedCallbackSchool = debounce(newValue => {
+  getSchoolListData(newValue)
+}, 500)
+
+const positionSearch = (name, init = '') => {
+  const item = formItems.value.options.find(e => e.key === 'positionId')
+  if (!item) return
+  if (!init) item.value = ''
+  item[item.itemTextName] = name
+}
+
+const formItems = ref({
+  options: [
+    {
+      type: 'combobox',
+      key: 'enterpriseId',
+      value: null,
+      default: null,
+      label: '企业名称 *',
+      col: 6,
+      outlined: true,
+      clearable: true,
+      canBeInputted: true, //
+      itemTextName: 'enterpriseName',
+      itemText: 'value',
+      itemValue: 'key',
+      rules: [v => !!v || '请选择企业名称'],
+      search: debouncedCallbackSchool,
+      items: []
+    },
+    {
+      type: 'combobox',
+      key: 'positionId',
+      value: null,
+      default: null,
+      label: '职位名称 *',
+      col: 6,
+      outlined: true,
+      clearable: true,
+      canBeInputted: true, //
+      itemTextName: 'positionName',
+      itemText: 'nameCn',
+      itemValue: 'id',
+      rules: [v => !!v || '请选择职位名称'],
+      search: val => positionSearch(val),
+      items: []
+    },
+    {
+      type: 'datepicker',
+      key: 'startTime',
+      value: shallowRef(null),
+      col: 6,
+      label: '起始时间 *',
+      rules: [v => !!v || '请选择起始时间']
+    },
+    {
+      type: 'datepicker',
+      key: 'endTime',
+      value: shallowRef(null),
+      col: 6,
+      label: '结束时间 *',
+      rules: [v => !!v || '请选择结束时间']
+    },
+    {
+      type: 'textarea',
+      key: 'content',
+      value: null,
+      default: null,
+      counter: 1600,
+      label: '工作内容 *',
+      outlined: true,
+      rules: [v => !!v || '请输入工作内容']
+    },
+  ]
+})
+// 左侧加mr
+formItems.value.options.forEach((e, index) => {
+  if (((index + 2) % 2 === 0) && Boolean(e.col) && e.col !== 12) e.flexStyle = 'mr-3'
+})
+
+console.log('dictItemsObj', dictItemsObj)
+// 获取数据
+const dataList = ref([])
+const getData = async () => {
+  const data = await getResumeWorkExp()
+  dataList.value = data
+}
+getData()
+
+// 新增 或 编辑
+const isEdit = ref(false)
+const titleStatus = ref(0)
+const handle = (item) => {
+  titleStatus.value = item ? 1 : 0
+  // if (!positionData.value || !positionData.value.length) getPositionData ()
+  if (item) { // 编辑
+    editId.value = item.id
+    formItems.value.options.forEach(e => { // 回显
+      if (e.canBeInputted) { // 特殊处理可输入下拉框
+        dealCanBeInputtedValueAndLabel(e, item)
+      }
+      else if (e.type === 'datepicker') e.value = timesTampChange(item[e.key]).slice(0, 10)
+      else if (item[e.key]) e.value = item[e.key]
+    })
+  } else { // 新增
+    editId.value = null
+    formItems.value.options.forEach(e => e.value = e.default || null)
+  }
+  nextTick(() => {
+    isEdit.value = true
+  })
+}
+
+// 保存-基础信息
+const handleSave = async () => {
+  const { valid } = await CtFormRef.value.formRef.validate()
+  if (!valid) return
+  const obj = {}
+  formItems.value.options.forEach(e => {
+    if (e.canBeInputted) { // 特殊处理可输入下拉框
+      dealCanBeInputtedSave(e, obj)
+    }
+    else if (e.type === 'datepicker') obj[e.key] = getTimeStamp(e.value)
+    else obj[e.key] = e.value
+  })
+  if (editId.value) obj.id = editId.value
+  await saveResumeWorkExp(obj)
+  Snackbar.success('保存成功!')
+  isEdit.value = false
+  await getData()
+}
+
+// 删除数据
+const handleDelete = ({ id }) => {
+  Confirm('系统提示', '是否确认删除此教育经历?').then(async () => {
+    await deleteResumeWorkExp(id)
+    nextTick(() => {
+      getData(id)
+      Snackbar.success('删除成功!')
+    })
+  })
+}
+
+// 获取字典内容
+const dictList = [
+  { type: 'positionData', key: 'positionId' }
+]
+const getDictData = async (obj) => {
+  const item = formItems.value.options.find(e => e.key === obj.key)
+  if (item) { //  && !item.items?.length
+    const { data } = await getDict(obj.type, null, obj.type)
+    item.items = data || []
+    dictItemsObj[obj.key] = data || []
+  }
+}
+const getOptions = () => {
+  dictList.forEach(obj =>  getDictData(obj))
+}
+getOptions()
+
+</script>
+
+<style scoped lang="scss">
+.font15 { font-size: 15px;; }
+.color9 { color: #999; }
+.color6 { color: #666666; }
+.educExpItem {
+  cursor: pointer;
+  border-radius: 6px;
+  padding: 2px 10px 8px;
+  &:hover {
+    background-color: #f8f8f8;
+  }
+}
+.educExpItem-edit {
+  border-radius: 6px;
+  padding: 2px 10px 8px;
+  background-color: #f8f8f8;
+}
+</style>

+ 3 - 1
src/views/resume/index.vue

@@ -44,6 +44,7 @@ import selfEvaluation from './components/selfEvaluation.vue'
 import jobIntention from './components/jobIntention.vue'
 import trainingExperience from './components/trainingExperience.vue'
 import educationExp from './components/educationExp.vue'
+import workExperience from './components/workExperience.vue'
 import projectExperience from './components/projectExperience.vue'
 import vocationalSkills from './components/vocationalSkills.vue'
 import { ref } from 'vue'
@@ -55,6 +56,7 @@ const comList = [
   { path: selfEvaluation, id: 'resumeSelfEvaluation', domId: 'selfEvaluation' },
   { path: jobIntention, id: 'resumeJobIntention', domId: 'jobIntention' },
   { path: educationExp, id: 'resumeEducationExp', domId: 'educationExp' },
+  { path: workExperience, id: 'resumeProjectExperience', domId: 'workExperience' },
   { path: projectExperience, id: 'resumeProjectExperience', domId: 'projectExperience' },
   { path: trainingExperience, id: 'resumeTrainingExperience', domId: 'trainingExperience' },
   { path: vocationalSkills, id: 'resumeVocationalSkills', domId: 'vocationalSkills' },
@@ -65,7 +67,7 @@ const items = [
   { text: '个人优势', icon: 'mdi-account-star-outline', id: 'selfEvaluation' },
   { text: '求职意向', icon: 'mdi-briefcase-variant-outline', id: 'jobIntention' },
   { text: '教育经历', icon: 'mdi-school-outline', id: 'educationExp' },
-  { text: '工作经历', icon: 'mdi-ballot-outline', id: '' },
+  { text: '工作经历', icon: 'mdi-ballot-outline', id: 'workExperience' },
   { text: '项目经历', icon: 'mdi-card-text-outline', id: 'projectExperience' },
   { text: '培训经历', icon: 'mdi-flag-outline', id: 'trainingExperience' },
   { text: '职业技能', icon: 'mdi-star-check-outline', id: 'vocationalSkills' }